use enum_dispatch::enum_dispatch; use pumpkin_data::{ chunk::DoublePerlinNoiseParameters, noise_router::{ BaseNoiseFunctionComponent, BaseNoiseRouters, BinaryOperation, LinearOperation, SplineRepr, UnaryOperation, }, }; use pumpkin_util::random::RandomDeriverImpl; use crate::{GlobalRandomConfig, generation::noise::perlin::DoublePerlinNoiseSampler}; use super::{ chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_noise_router::{ChunkNoiseFunctionComponent, StaticChunkNoiseFunctionComponentImpl}, density_function::{ IndexToNoisePos, NoiseFunctionComponentRange, NoisePos, PassThrough, StaticIndependentChunkNoiseFunctionComponentImpl, Wrapper, math::{Binary, Clamp, Constant, Linear, Unary}, misc::{ClampedYGradient, EndIsland, RangeChoice, WeirdScaled}, noise::{InterpolatedNoiseSampler, Noise, ShiftA, ShiftB, ShiftedNoise}, spline::{Spline, SplineFunction, SplinePoint, SplineValue}, }, }; #[enum_dispatch( StaticIndependentChunkNoiseFunctionComponentImpl, NoiseFunctionComponentRange )] pub enum IndependentProtoNoiseFunctionComponent { Constant(Constant), EndIsland(EndIsland), Noise(Noise), ShiftA(ShiftA), ShiftB(ShiftB), InterpolatedNoise(InterpolatedNoiseSampler), ClampedYGradient(ClampedYGradient), } #[enum_dispatch(StaticChunkNoiseFunctionComponentImpl, NoiseFunctionComponentRange)] pub enum DependentProtoNoiseFunctionComponent { Linear(Linear), Unary(Unary), Binary(Binary), ShiftedNoise(ShiftedNoise), WeirdScaled(WeirdScaled), Clamp(Clamp), RangeChoice(RangeChoice), Spline(SplineFunction), } #[enum_dispatch(NoiseFunctionComponentRange)] pub enum ProtoNoiseFunctionComponent { Independent(IndependentProtoNoiseFunctionComponent), Dependent(DependentProtoNoiseFunctionComponent), Wrapper(Wrapper), PassThrough(PassThrough), } pub struct DoublePerlinNoiseBuilder<'a> { random_config: &'a GlobalRandomConfig, } impl<'a> DoublePerlinNoiseBuilder<'a> { #[must_use] pub const fn new(rand: &'a GlobalRandomConfig) -> Self { Self { random_config: rand, } } #[must_use] pub fn get_noise_sampler_for_id(&self, id: &str) -> DoublePerlinNoiseSampler { let parameters = DoublePerlinNoiseParameters::id_to_parameters(id) .unwrap_or_else(|| panic!("Unknown noise id: {id}")); // Note that the parameters' id is different than `id` let mut random = self .random_config .base_random_deriver .split_string(parameters.id()); DoublePerlinNoiseSampler::from_params(&mut random, parameters, false) } } pub struct ProtoNoiseRouter { pub full_component_stack: Box<[ProtoNoiseFunctionComponent]>, pub barrier_noise: usize, pub fluid_level_floodedness_noise: usize, pub fluid_level_spread_noise: usize, pub lava_noise: usize, pub erosion: usize, pub depth: usize, pub final_density: usize, pub vein_toggle: usize, pub vein_ridged: usize, pub vein_gap: usize, } pub struct ProtoSurfaceEstimator { pub full_component_stack: Box<[ProtoNoiseFunctionComponent]>, } pub struct ProtoMultiNoiseRouter { pub full_component_stack: Box<[ProtoNoiseFunctionComponent]>, pub temperature: usize, pub vegetation: usize, pub continents: usize, pub erosion: usize, pub depth: usize, pub ridges: usize, } pub struct ProtoNoiseRouters { pub noise: ProtoNoiseRouter, pub surface_estimator: ProtoSurfaceEstimator, pub multi_noise: ProtoMultiNoiseRouter, } fn build_spline_recursive(spline_repr: &SplineRepr) -> SplineValue { match spline_repr { SplineRepr::Standard { location_function_index, points, } => { let points = points .iter() .map(|point| { let value = build_spline_recursive(point.value); SplinePoint::new(point.location, value, point.derivative) }) .collect(); SplineValue::Spline(Spline::new(*location_function_index, points)) } // Top level splines always take a density function as input SplineRepr::Fixed { value } => SplineValue::Fixed(*value), } } impl ProtoNoiseRouters { #[must_use] pub fn generate_proto_stack( base_stack: &[BaseNoiseFunctionComponent], random_config: &GlobalRandomConfig, ) -> Box<[ProtoNoiseFunctionComponent]> { let perlin_noise_builder = DoublePerlinNoiseBuilder::new(random_config); // Contiguous memory for our function components let mut stack = Vec::::with_capacity(base_stack.len()); for component in base_stack { let converted = match component { BaseNoiseFunctionComponent::Spline { spline } => { let spline = match build_spline_recursive(spline) { SplineValue::Spline(spline) => spline, // Top level splines always take in a density function SplineValue::Fixed(_) => unreachable!(), }; ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::Spline(SplineFunction::new( spline, &stack, )), ) } BaseNoiseFunctionComponent::EndIslands => ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::EndIsland(EndIsland::new( random_config.seed, )), ), BaseNoiseFunctionComponent::Noise { data } => { let sampler = perlin_noise_builder.get_noise_sampler_for_id(data.noise_id); ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::Noise(Noise::new(sampler, data)), ) } BaseNoiseFunctionComponent::ShiftA { noise_id } => { let sampler = perlin_noise_builder.get_noise_sampler_for_id(noise_id); ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::ShiftA(ShiftA::new(sampler)), ) } BaseNoiseFunctionComponent::ShiftB { noise_id } => { let sampler = perlin_noise_builder.get_noise_sampler_for_id(noise_id); ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::ShiftB(ShiftB::new(sampler)), ) } BaseNoiseFunctionComponent::BlendDensity { input_index } => { // TODO: Replace this when the blender is implemented let min_value = stack[*input_index].min(); let max_value = stack[*input_index].max(); ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( *input_index, min_value, max_value, )) } BaseNoiseFunctionComponent::BlendAlpha => { // TODO: Replace this with the cache when the blender is implemented ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::Constant(Constant::new(1.0)), ) } BaseNoiseFunctionComponent::BlendOffset => { // TODO: Replace this with the cache when the blender is implemented ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::Constant(Constant::new(0.0)), ) } BaseNoiseFunctionComponent::Beardifier => { // TODO: Replace this when world structures are implemented ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::Constant(Constant::new(0.0)), ) } BaseNoiseFunctionComponent::ShiftedNoise { shift_x_index, shift_y_index, shift_z_index, data, } => { let sampler = perlin_noise_builder.get_noise_sampler_for_id(data.noise_id); ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::ShiftedNoise(ShiftedNoise::new( *shift_x_index, *shift_y_index, *shift_z_index, sampler, data, )), ) } BaseNoiseFunctionComponent::RangeChoice { input_index, when_in_range_index, when_out_range_index, data, } => { let min_value = stack[*when_in_range_index] .min() .min(stack[*when_out_range_index].min()); let max_value = stack[*when_in_range_index] .max() .max(stack[*when_out_range_index].max()); ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::RangeChoice(RangeChoice::new( *input_index, *when_in_range_index, *when_out_range_index, min_value, max_value, data, )), ) } BaseNoiseFunctionComponent::Binary { argument1_index, argument2_index, data, } => { let arg1_min = stack[*argument1_index].min(); let arg1_max = stack[*argument1_index].max(); let arg2_min = stack[*argument2_index].min(); let arg2_max = stack[*argument2_index].max(); let (min, max) = match data.operation { BinaryOperation::Add => (arg1_min + arg2_min, arg1_max + arg2_max), BinaryOperation::Mul => { let min = if arg1_min > 0.0 && arg2_min > 0.0 { arg1_min * arg2_min } else if arg1_max < 0.0 && arg2_max < 0.0 { arg1_max * arg2_max } else { (arg1_min * arg2_max).min(arg1_max * arg2_min) }; let max = if arg1_min > 0.0 && arg2_min > 0.0 { arg1_max * arg2_max } else if arg1_max < 0.0 && arg2_max < 0.0 { arg1_min * arg2_min } else { (arg1_min * arg2_min).max(arg1_max * arg2_max) }; (min, max) } BinaryOperation::Min => (arg1_min.min(arg2_min), arg1_max.min(arg2_max)), BinaryOperation::Max => (arg1_min.max(arg2_min), arg1_max.max(arg2_max)), }; ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::Binary(Binary::new( *argument1_index, *argument2_index, min, max, data, )), ) } BaseNoiseFunctionComponent::ClampedYGradient { data } => { ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::ClampedYGradient( ClampedYGradient::new(data), ), ) } BaseNoiseFunctionComponent::Constant { value } => { ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::Constant(Constant::new(*value)), ) } BaseNoiseFunctionComponent::Wrapper { input_index, wrapper, } => { let min_value = stack[*input_index].min(); let max_value = stack[*input_index].max(); ProtoNoiseFunctionComponent::Wrapper(Wrapper::new( *input_index, *wrapper, min_value, max_value, )) } BaseNoiseFunctionComponent::Linear { input_index, data } => { let arg1_min = stack[*input_index].min(); let arg1_max = stack[*input_index].max(); let (min, max) = match data.operation { LinearOperation::Add => { (arg1_min + data.argument, arg1_max + data.argument) } LinearOperation::Mul => { let min = if arg1_min > 0.0 && data.argument > 0.0 { arg1_min * data.argument } else if arg1_max < 0.0 && data.argument < 0.0 { arg1_max * data.argument } else { (arg1_min * data.argument).min(arg1_max * data.argument) }; let max = if arg1_min > 0.0 && data.argument > 0.0 { arg1_max * data.argument } else if arg1_max < 0.0 && data.argument < 0.0 { arg1_min * data.argument } else { (arg1_min * data.argument).max(arg1_max * data.argument) }; (min, max) } }; ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::Linear(Linear::new( *input_index, min, max, data, )), ) } BaseNoiseFunctionComponent::Clamp { input_index, data } => { ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::Clamp(Clamp::new(*input_index, data)), ) } BaseNoiseFunctionComponent::Unary { input_index, data } => { let arg1_min = stack[*input_index].min(); let arg1_max = stack[*input_index].max(); let applied_min_value = data.apply_density(arg1_min); let applied_max_value = data.apply_density(arg1_max); let (min_value, max_value) = match data.operation { // TODO: I'm pretty sure this can be more restrictive UnaryOperation::Abs | UnaryOperation::Square => { (arg1_min.max(0.0), applied_min_value.max(applied_max_value)) } UnaryOperation::Squeeze | UnaryOperation::Cube | UnaryOperation::QuarterNegative | UnaryOperation::HalfNegative => (applied_min_value, applied_max_value), }; ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::Unary(Unary::new( *input_index, min_value, max_value, data, )), ) } BaseNoiseFunctionComponent::WeirdScaled { input_index, data } => { let sampler = perlin_noise_builder.get_noise_sampler_for_id(data.noise_id); ProtoNoiseFunctionComponent::Dependent( DependentProtoNoiseFunctionComponent::WeirdScaled(WeirdScaled::new( *input_index, sampler, data, )), ) } BaseNoiseFunctionComponent::InterpolatedNoiseSampler { data } => { let mut random_generator = random_config .base_random_deriver .split_string("minecraft:terrain"); ProtoNoiseFunctionComponent::Independent( IndependentProtoNoiseFunctionComponent::InterpolatedNoise( InterpolatedNoiseSampler::new(data, &mut random_generator), ), ) } }; stack.push(converted); } stack.into() } #[must_use] pub fn generate(base: &BaseNoiseRouters, random_config: &GlobalRandomConfig) -> Self { let noise_stack = Self::generate_proto_stack(base.noise.full_component_stack, random_config); let surface_stack = Self::generate_proto_stack(base.surface_estimator.full_component_stack, random_config); let multi_noise_stack = Self::generate_proto_stack(base.multi_noise.full_component_stack, random_config); Self { noise: ProtoNoiseRouter { full_component_stack: noise_stack, barrier_noise: base.noise.barrier_noise, fluid_level_floodedness_noise: base.noise.fluid_level_floodedness_noise, fluid_level_spread_noise: base.noise.fluid_level_spread_noise, lava_noise: base.noise.lava_noise, erosion: base.noise.erosion, depth: base.noise.depth, final_density: base.noise.final_density, vein_toggle: base.noise.vein_toggle, vein_ridged: base.noise.vein_ridged, vein_gap: base.noise.vein_gap, }, surface_estimator: ProtoSurfaceEstimator { full_component_stack: surface_stack, }, multi_noise: ProtoMultiNoiseRouter { full_component_stack: multi_noise_stack, temperature: base.multi_noise.temperature, vegetation: base.multi_noise.vegetation, continents: base.multi_noise.continents, erosion: base.multi_noise.erosion, depth: base.multi_noise.depth, ridges: base.multi_noise.ridges, }, } } }