nucleation 0.3.19

A high-performance Minecraft schematic parser and utility library
Documentation
use crate::building::{
    BezierCurve, BilinearGradientBrush, BlockPalette, Brush, ColorBrush, Cone, Cuboid,
    CurveGradientBrush, Cylinder, Difference, Disk, Ellipsoid, FieldBrush, Hollow, Intersection,
    Line, LinearGradientBrush, MultiPointGradientBrush, ParametricShape, Plane, PointGradientBrush,
    PolygonPrism, Pyramid, SdfShape, ShadedBrush, Shape, SolidBrush, Sphere, SpotlightBrush, Torus,
    Triangle, TubePath, Union,
};
use crate::BlockState;

// ============================================================================
// Preset palette lookup
// ============================================================================

/// Construct one of the preset [`BlockPalette`]s by name — the same set every
/// consumer (bridge, scripting, SDF material rules) exposes: `all`, `solid`,
/// `structural`, `decorative`, `concrete`, `wool`, `terracotta`, `grayscale`,
/// `wood`. Errors on any other name.
pub fn palette_by_name(name: &str) -> Result<BlockPalette, String> {
    Ok(match name {
        "all" => BlockPalette::new_all(),
        "solid" => BlockPalette::new_solid(),
        "structural" => BlockPalette::new_structural(),
        "decorative" => BlockPalette::new_decorative(),
        "concrete" => BlockPalette::new_concrete(),
        "wool" => BlockPalette::new_wool(),
        "terracotta" => BlockPalette::new_terracotta(),
        "grayscale" => BlockPalette::new_grayscale(),
        "wood" => BlockPalette::new_wood(),
        _ => {
            return Err(format!(
                "Unknown palette '{name}' (expected all, solid, structural, decorative, \
                 concrete, wool, terracotta, grayscale or wood)"
            ))
        }
    })
}

// ============================================================================
// Delegate macro for ShapeEnum
// ============================================================================

macro_rules! delegate_shape {
    ($self:expr, $method:ident $(, $arg:expr)*) => {
        match $self {
            #[cfg(feature = "voxelize")]
            ShapeEnum::Mesh(s) => s.$method($($arg),*),
            ShapeEnum::Sphere(s) => s.$method($($arg),*),
            ShapeEnum::Cuboid(s) => s.$method($($arg),*),
            ShapeEnum::Ellipsoid(s) => s.$method($($arg),*),
            ShapeEnum::Cylinder(s) => s.$method($($arg),*),
            ShapeEnum::Cone(s) => s.$method($($arg),*),
            ShapeEnum::Torus(s) => s.$method($($arg),*),
            ShapeEnum::Pyramid(s) => s.$method($($arg),*),
            ShapeEnum::Disk(s) => s.$method($($arg),*),
            ShapeEnum::Plane(s) => s.$method($($arg),*),
            ShapeEnum::PolygonPrism(s) => s.$method($($arg),*),
            ShapeEnum::Triangle(s) => s.$method($($arg),*),
            ShapeEnum::Line(s) => s.$method($($arg),*),
            ShapeEnum::BezierCurve(s) => s.$method($($arg),*),
            ShapeEnum::TubePath(s) => s.$method($($arg),*),
            ShapeEnum::Sdf(s) => s.$method($($arg),*),
            ShapeEnum::Hollow(s) => s.$method($($arg),*),
            ShapeEnum::Union(s) => s.$method($($arg),*),
            ShapeEnum::Intersection(s) => s.$method($($arg),*),
            ShapeEnum::Difference(s) => s.$method($($arg),*),
        }
    };
}

// ============================================================================
// Shapes
// ============================================================================

#[derive(Clone)]
pub enum ShapeEnum {
    Sphere(Sphere),
    Cuboid(Cuboid),
    Ellipsoid(Ellipsoid),
    Cylinder(Cylinder),
    Cone(Cone),
    Torus(Torus),
    Pyramid(Pyramid),
    Disk(Disk),
    Plane(Plane),
    PolygonPrism(PolygonPrism),
    Triangle(Triangle),
    Line(Line),
    BezierCurve(BezierCurve),
    TubePath(TubePath),
    Sdf(SdfShape),
    /// A voxelized triangle mesh (GLB/OBJ). Only with the `voxelize` feature.
    #[cfg(feature = "voxelize")]
    Mesh(crate::voxelize::MeshShape),
    Hollow(Hollow),
    Union(Union),
    Intersection(Intersection),
    Difference(Difference),
}

impl ShapeEnum {
    /// Returns parametric position t in [0, 1] for parametric shapes, None for non-parametric.
    pub fn parameter_at(&self, x: i32, y: i32, z: i32) -> Option<f64> {
        match self {
            Self::Line(s) => Some(s.parameter_at(x, y, z)),
            Self::Cylinder(s) => Some(s.parameter_at(x, y, z)),
            Self::Cone(s) => Some(s.parameter_at(x, y, z)),
            Self::Torus(s) => Some(s.parameter_at(x, y, z)),
            Self::Pyramid(s) => Some(s.parameter_at(x, y, z)),
            Self::BezierCurve(s) => Some(s.parameter_at(x, y, z)),
            Self::TubePath(s) => Some(s.parameter_at(x, y, z)),
            Self::Hollow(s) => s.inner.parameter_at(x, y, z),
            Self::Union(s) => {
                if s.a.contains(x, y, z) {
                    s.a.parameter_at(x, y, z)
                } else {
                    s.b.parameter_at(x, y, z)
                }
            }
            Self::Intersection(s) => s.a.parameter_at(x, y, z),
            Self::Difference(s) => s.a.parameter_at(x, y, z),
            _ => None,
        }
    }
}

impl Shape for ShapeEnum {
    fn contains(&self, x: i32, y: i32, z: i32) -> bool {
        delegate_shape!(self, contains, x, y, z)
    }

    fn points(&self) -> Vec<(i32, i32, i32)> {
        delegate_shape!(self, points)
    }

    fn normal_at(&self, x: i32, y: i32, z: i32) -> (f64, f64, f64) {
        delegate_shape!(self, normal_at, x, y, z)
    }

    fn bounds(&self) -> (i32, i32, i32, i32, i32, i32) {
        delegate_shape!(self, bounds)
    }

    fn for_each_point<F>(&self, f: F)
    where
        F: FnMut(i32, i32, i32),
    {
        delegate_shape!(self, for_each_point, f)
    }
}

// ============================================================================
// Brushes
// ============================================================================

#[derive(Clone)]
pub enum BrushEnum {
    Solid(SolidBrush),
    Color(ColorBrush),
    Linear(LinearGradientBrush),
    Bilinear(BilinearGradientBrush),
    Point(PointGradientBrush),
    MultiPoint(MultiPointGradientBrush),
    Shaded(ShadedBrush),
    CurveGradient(CurveGradientBrush),
    Spotlight(SpotlightBrush),
    Field(FieldBrush),
}

impl BrushEnum {
    /// Point every palette-driven variant at `palette`. No-op for `Solid`,
    /// which places a fixed block state and never consults a palette.
    pub fn set_palette(&mut self, palette: std::sync::Arc<crate::building::BlockPalette>) {
        match self {
            BrushEnum::Solid(_) => {}
            BrushEnum::Color(b) => b.set_palette(palette),
            BrushEnum::Linear(b) => b.set_palette(palette),
            BrushEnum::Bilinear(b) => b.set_palette(palette),
            BrushEnum::Point(b) => b.set_palette(palette),
            BrushEnum::MultiPoint(b) => b.set_palette(palette),
            BrushEnum::Shaded(b) => b.set_palette(palette),
            BrushEnum::CurveGradient(b) => b.set_palette(palette),
            BrushEnum::Spotlight(b) => b.set_palette(palette),
            BrushEnum::Field(b) => b.set_palette(palette),
        }
    }
}

impl Brush for BrushEnum {
    fn get_block(&self, x: i32, y: i32, z: i32, normal: (f64, f64, f64)) -> Option<BlockState> {
        match self {
            BrushEnum::Solid(b) => b.get_block(x, y, z, normal),
            BrushEnum::Color(b) => b.get_block(x, y, z, normal),
            BrushEnum::Linear(b) => b.get_block(x, y, z, normal),
            BrushEnum::Bilinear(b) => b.get_block(x, y, z, normal),
            BrushEnum::Point(b) => b.get_block(x, y, z, normal),
            BrushEnum::MultiPoint(b) => b.get_block(x, y, z, normal),
            BrushEnum::Shaded(b) => b.get_block(x, y, z, normal),
            BrushEnum::CurveGradient(b) => b.get_block(x, y, z, normal),
            BrushEnum::Spotlight(b) => b.get_block(x, y, z, normal),
            BrushEnum::Field(b) => b.get_block(x, y, z, normal),
        }
    }
}

impl BrushEnum {
    /// Get a block using both spatial and parametric information.
    /// For CurveGradientBrush, the `t` parameter takes priority when available.
    pub fn get_block_with_parameter(
        &self,
        x: i32,
        y: i32,
        z: i32,
        normal: (f64, f64, f64),
        t: Option<f64>,
    ) -> Option<BlockState> {
        match self {
            BrushEnum::CurveGradient(b) => b.get_block_parametric(x, y, z, normal, t),
            _ => self.get_block(x, y, z, normal),
        }
    }
}

#[cfg(test)]
mod sampled_curve_enum_tests {
    use super::*;

    #[test]
    fn tube_path_retains_parameter_through_shape_enum() {
        let curve = crate::building::Curve3D::new(
            vec![(0.0, 0.0, 0.0), (4.0, 0.0, 0.0), (4.0, 0.0, 4.0)],
            true,
        )
        .unwrap();
        let shape = ShapeEnum::TubePath(crate::building::TubePath::new(curve, 0.6).unwrap());

        assert!(shape.contains(2, 0, 2));
        assert!(shape.parameter_at(2, 0, 2).unwrap() > 0.66);
    }
}