Pumpkin/pumpkin-codegen/src/noise_router.rs
Alexander Medvedev 2fcc9bcf4a chore: enforce some more clippy lints
pumpkin contributors will love me :cap:
2026-05-20 22:27:54 +02:00

1582 lines
54 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{
collections::BTreeMap,
fs,
hash::{DefaultHasher, Hash, Hasher},
};
use heck::ToShoutySnakeCase;
use proc_macro2::{Punct, Spacing, Span, TokenStream};
use quote::{ToTokens, TokenStreamExt, quote};
use serde::Deserialize;
use syn::Ident;
/// Wraps an `f32` to provide a bitwise-exact `Hash` implementation for use as a map key.
struct HashableF32(pub f32);
// Normally this is bad, but we just care about checking if components are the same
impl Hash for HashableF32 {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_le_bytes().hash(state);
}
}
impl ToTokens for HashableF32 {
fn to_tokens(&self, tokens: &mut TokenStream) {
let value = self.0;
if value.is_finite() {
value.to_tokens(tokens);
} else {
tokens.append(Ident::new("f32", Span::call_site()));
tokens.append(Punct::new(':', Spacing::Joint));
tokens.append(Punct::new(':', Spacing::Joint));
if value.is_nan() {
tokens.append(Ident::new("NAN", Span::call_site()));
} else if value > 0.0 {
tokens.append(Ident::new("INFINITY", Span::call_site()));
} else {
tokens.append(Ident::new("NEG_INFINITY", Span::call_site()));
}
}
}
}
impl<'de> Deserialize<'de> for HashableF32 {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
f32::deserialize(deserializer).map(Self)
}
}
/// Wraps an `f64` to provide a bitwise-exact `Hash` implementation for use as a map key.
struct HashableF64(pub f64);
// Normally this is bad, but we just care about checking if components are the same
impl Hash for HashableF64 {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_le_bytes().hash(state);
}
}
impl ToTokens for HashableF64 {
fn to_tokens(&self, tokens: &mut TokenStream) {
let value = self.0;
if value.is_finite() {
value.to_tokens(tokens);
} else {
tokens.append(Ident::new("f64", Span::call_site()));
tokens.append(Punct::new(':', Spacing::Joint));
tokens.append(Punct::new(':', Spacing::Joint));
if value.is_nan() {
tokens.append(Ident::new("NAN", Span::call_site()));
} else if value > 0.0 {
tokens.append(Ident::new("INFINITY", Span::call_site()));
} else {
tokens.append(Ident::new("NEG_INFINITY", Span::call_site()));
}
}
}
}
impl<'de> Deserialize<'de> for HashableF64 {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
f64::deserialize(deserializer).map(Self)
}
}
/// Deserialized representation of a cubic spline used inside density functions.
#[derive(Deserialize, Hash)]
#[serde(tag = "_type", content = "value")]
enum SplineRepr {
/// A standard multipoint spline evaluated against a location density function.
#[serde(rename(deserialize = "standard"))]
Standard {
/// The density function that drives the spline location axis.
#[serde(rename(deserialize = "locationFunction"))]
location_function: Box<DensityFunctionRepr>,
/// X-axis sample locations for each spline segment.
locations: Box<[HashableF32]>,
/// Nested spline values at each sample location.
values: Box<[Self]>,
/// Derivative (tangent) values at each sample location.
derivatives: Box<[HashableF32]>,
},
/// A spline that returns a single constant value regardless of input.
#[serde(rename(deserialize = "fixed"))]
Fixed {
/// The constant output value.
value: HashableF32,
},
}
impl SplineRepr {
/// Emits the `SplineRepr` token stream, registering any subcomponents into `stack`.
///
/// # Arguments
/// – `stack` – accumulator of all unique density-function component token streams.
/// – `hash_to_index_map` – maps a component's hash to its index in `stack`.
fn get_token_stream(
&self,
stack: &mut Vec<TokenStream>,
hash_to_index_map: &mut BTreeMap<u64, usize>,
) -> TokenStream {
match self {
Self::Fixed { value } => {
quote! {
SplineRepr::Fixed {value: #value}
}
}
Self::Standard {
location_function,
locations,
values,
derivatives,
} => {
assert_eq!(values.len(), locations.len());
assert_eq!(values.len(), derivatives.len());
let points = locations
.into_iter()
.zip(values)
.zip(derivatives)
.map(|((location, value), derivative)| (location, value, derivative));
let function_index =
location_function.get_index_for_component(stack, hash_to_index_map);
let point_reprs = points
.into_iter()
.map(|(location, value, derivative)| {
let value_repr = value.get_token_stream(stack, hash_to_index_map);
quote! {
SplinePoint {
location: #location,
value: &#value_repr,
derivative: #derivative,
}
}
})
.collect::<Vec<_>>();
quote! {
SplineRepr::Standard {
location_function_index: #function_index,
points: &[#(#point_reprs),*],
}
}
}
}
}
}
/// Arithmetic operation applied to two density function arguments.
#[derive(Deserialize, Hash, Copy, Clone)]
enum BinaryOperation {
/// Adds the two arguments.
#[serde(rename(deserialize = "ADD"))]
Add,
/// Multiplies the two arguments.
#[serde(rename(deserialize = "MUL"))]
Mul,
/// Takes the minimum of the two arguments.
#[serde(rename(deserialize = "MIN"))]
Min,
/// Takes the maximum of the two arguments.
#[serde(rename(deserialize = "MAX"))]
Max,
}
impl BinaryOperation {
/// Emits the token stream for this binary operation variant.
fn get_token_stream(&self) -> TokenStream {
match self {
Self::Add => {
quote! {
BinaryOperation::Add
}
}
Self::Mul => {
quote! {
BinaryOperation::Mul
}
}
Self::Min => {
quote! {
BinaryOperation::Min
}
}
Self::Max => {
quote! {
BinaryOperation::Max
}
}
}
}
}
/// Arithmetic operation applied to a single density function argument and a scalar.
#[derive(Deserialize, Hash, Copy, Clone)]
enum LinearOperation {
/// Adds the scalar argument to the density value.
#[serde(rename(deserialize = "ADD"))]
Add,
/// Multiplies the density value by the scalar argument.
#[serde(rename(deserialize = "MUL"))]
Mul,
}
impl LinearOperation {
/// Emits the token stream for this linear operation variant.
fn into_token_stream(self) -> TokenStream {
match self {
Self::Add => {
quote! {
LinearOperation::Add
}
}
Self::Mul => {
quote! {
LinearOperation::Mul
}
}
}
}
}
/// Single-argument transformation applied to a density value.
#[derive(Deserialize, Hash, Copy, Clone)]
enum UnaryOperation {
/// Returns the reciprocal (1/x) of the value.
#[serde(rename(deserialize = "INVERT"))]
Invert,
/// Returns the absolute value.
#[serde(rename(deserialize = "ABS"))]
Abs,
/// Squares the value.
#[serde(rename(deserialize = "SQUARE"))]
Square,
/// Cubes the value.
#[serde(rename(deserialize = "CUBE"))]
Cube,
/// Halves the value only if it is negative, passes it through otherwise.
#[serde(rename(deserialize = "HALF_NEGATIVE"))]
HalfNegative,
/// Quarters the value only if it is negative, passes it through otherwise.
#[serde(rename(deserialize = "QUARTER_NEGATIVE"))]
QuarterNegative,
/// Applies a smooth cubic "squeeze" mapping to `[-1, 1]`.
#[serde(rename(deserialize = "SQUEEZE"))]
Squeeze,
}
impl UnaryOperation {
/// Emits the token stream for this unary operation variant.
fn into_token_stream(self) -> TokenStream {
match self {
Self::Invert => {
quote! {
UnaryOperation::Invert
}
}
Self::Abs => {
quote! {
UnaryOperation::Abs
}
}
Self::Square => {
quote! {
UnaryOperation::Square
}
}
Self::Cube => {
quote! {
UnaryOperation::Cube
}
}
Self::HalfNegative => {
quote! {
UnaryOperation::HalfNegative
}
}
Self::QuarterNegative => {
quote! {
UnaryOperation::QuarterNegative
}
}
Self::Squeeze => {
quote! {
UnaryOperation::Squeeze
}
}
}
}
}
/// Rarity-value mapper used by the `WeirdScaled` density function.
#[derive(Deserialize, Hash, Copy, Clone)]
enum WeirdScaledMapper {
/// Cave-type scaling curve (TYPE2).
#[serde(rename(deserialize = "TYPE2"))]
Caves,
/// Tunnel-type scaling curve (TYPE1).
#[serde(rename(deserialize = "TYPE1"))]
Tunnels,
}
impl WeirdScaledMapper {
/// Emits the token stream for this mapper variant.
fn into_token_stream(self) -> TokenStream {
match self {
Self::Caves => {
quote! {
WeirdScaledMapper::Caves
}
}
Self::Tunnels => {
quote! {
WeirdScaledMapper::Tunnels
}
}
}
}
}
/// Caching or interpolation wrapper applied around an inner density function.
#[derive(Copy, Clone, Deserialize, PartialEq, Eq, Hash)]
enum WrapperType {
/// Trilinear interpolation over noise cells.
Interpolated,
/// Flat (2D) per-column cache.
#[serde(rename(deserialize = "FlatCache"))]
CacheFlat,
/// 2D (XZ) per-chunk cache.
Cache2D,
/// Evaluate once and cache for the entire invocation.
CacheOnce,
/// Per-noise-cell cache.
CellCache,
}
impl WrapperType {
/// Emits the token stream for this wrapper type variant.
fn into_token_stream(self) -> TokenStream {
match self {
Self::Interpolated => {
quote! {
WrapperType::Interpolated
}
}
Self::CacheFlat => {
quote! {
WrapperType::CacheFlat
}
}
Self::Cache2D => {
quote! {
WrapperType::Cache2D
}
}
Self::CacheOnce => {
quote! {
WrapperType::CacheOnce
}
}
Self::CellCache => {
quote! {
WrapperType::CellCache
}
}
}
}
}
/// Deserialized parameters for a simple noise density function.
#[derive(Deserialize, Hash)]
struct NoiseData {
/// Resource location ID of the noise generator.
#[serde(rename(deserialize = "noise"))]
noise_id: String,
/// Horizontal (XZ) frequency scale factor.
#[serde(rename(deserialize = "xzScale"))]
xz_scale: HashableF64,
/// Vertical (Y) frequency scale factor.
#[serde(rename(deserialize = "yScale"))]
y_scale: HashableF64,
}
/// Deserialized parameters for a shifted-noise density function.
#[derive(Deserialize, Hash)]
struct ShiftedNoiseData {
/// Horizontal (XZ) frequency scale factor.
#[serde(rename(deserialize = "xzScale"))]
xz_scale: HashableF64,
/// Vertical (Y) frequency scale factor.
#[serde(rename(deserialize = "yScale"))]
y_scale: HashableF64,
/// Resource location ID of the noise generator.
#[serde(rename(deserialize = "noise"))]
noise_id: String,
}
/// Deserialized parameters for a weird-scaled-sampler density function.
#[derive(Deserialize, Hash, Clone)]
struct WeirdScaledData {
/// Resource location ID of the noise generator.
#[serde(rename(deserialize = "noise"))]
noise_id: String,
/// The rarity-value mapper that scales the noise output.
#[serde(rename(deserialize = "rarityValueMapper"))]
mapper: WeirdScaledMapper,
}
/// Deserialized parameters for the interpolated noise sampler density function.
#[derive(Deserialize, Hash)]
struct InterpolatedNoiseSamplerData {
/// XZ scale after cell-size scaling has been applied.
#[serde(rename(deserialize = "scaledXzScale"))]
scaled_xz_scale: HashableF64,
/// Y scale after cell-size scaling has been applied.
#[serde(rename(deserialize = "scaledYScale"))]
scaled_y_scale: HashableF64,
/// Horizontal cell-size factor.
#[serde(rename(deserialize = "xzFactor"))]
xz_factor: HashableF64,
/// Vertical cell-size factor.
#[serde(rename(deserialize = "yFactor"))]
y_factor: HashableF64,
/// Multiplier applied to smear-scale for blending.
#[serde(rename(deserialize = "smearScaleMultiplier"))]
smear_scale_multiplier: HashableF64,
/// Maximum possible output value.
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
// These are unused currently
//#[serde(rename(deserialize = "xzScale"))]
//xz_scale: HashableF64,
//#[serde(rename(deserialize = "yScale"))]
//y_scale: HashableF64,
}
/// Deserialized parameters for a clamped Y-gradient density function.
#[derive(Deserialize, Hash)]
struct ClampedYGradientData {
/// Y coordinate at which the gradient starts.
#[serde(rename(deserialize = "fromY"))]
from_y: i32,
/// Y coordinate at which the gradient ends.
#[serde(rename(deserialize = "toY"))]
to_y: i32,
/// Density value at `from_y`.
#[serde(rename(deserialize = "fromValue"))]
from_value: HashableF64,
/// Density value at `to_y`.
#[serde(rename(deserialize = "toValue"))]
to_value: HashableF64,
}
/// Deserialized parameters for a binary density function operation.
#[derive(Deserialize, Hash)]
struct BinaryData {
/// The binary operation to apply to the two arguments.
#[serde(rename(deserialize = "type"))]
operation: BinaryOperation,
/// Minimum possible output value (informational, not enforced).
#[serde(rename(deserialize = "minValue"))]
min_value: HashableF64,
/// Maximum possible output value (informational, not enforced).
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
}
/// Deserialized parameters for a linear density function operation.
#[derive(Deserialize, Hash)]
struct LinearData {
/// The linear operation (add or multiply) to apply with `argument`.
#[serde(rename(deserialize = "specificType"))]
operation: LinearOperation,
/// The scalar operand for the linear operation.
argument: HashableF64,
/// Minimum possible output value (informational, not enforced).
#[serde(rename(deserialize = "minValue"))]
min_value: HashableF64,
/// Maximum possible output value (informational, not enforced).
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
}
#[derive(Deserialize, Hash)]
struct FindTopSurfaceData {
/// Lower Y bound to stop searching at.
#[serde(rename(deserialize = "lowerBound"))]
lower_bound: i32,
/// Step size between Y levels when searching.
#[serde(rename(deserialize = "cellHeight"))]
cell_height: i32,
}
/// Deserialized parameters for a unary density function transformation.
#[derive(Deserialize, Hash)]
struct UnaryData {
/// The unary transformation to apply.
#[serde(rename(deserialize = "type"))]
operation: UnaryOperation,
/// Minimum possible output value (informational, not enforced).
#[serde(rename(deserialize = "minValue"))]
min_value: HashableF64,
/// Maximum possible output value (informational, not enforced).
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
}
/// Deserialized parameters for a clamp density function.
#[derive(Deserialize, Hash)]
struct ClampData {
/// Lower bound of the clamp range.
#[serde(rename(deserialize = "minValue"))]
min_value: HashableF64,
/// Upper bound of the clamp range.
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
}
/// Deserialized range bounds for the `RangeChoice` density function.
#[derive(Deserialize, Hash)]
struct RangeChoiceData {
/// Inclusive lower bound of the "in-range" interval.
#[serde(rename(deserialize = "minInclusive"))]
min_inclusive: HashableF64,
/// Exclusive upper bound of the "in-range" interval.
#[serde(rename(deserialize = "maxExclusive"))]
max_exclusive: HashableF64,
}
/// Deserialized output-range metadata for a spline density function.
#[derive(Deserialize, Hash)]
struct SplineData {
/// Minimum possible output value of the spline.
#[serde(rename(deserialize = "minValue"))]
min_value: HashableF64,
/// Maximum possible output value of the spline.
#[serde(rename(deserialize = "maxValue"))]
max_value: HashableF64,
}
/// Deserialized representation of any density function node in the noise router tree.
#[derive(Deserialize, Hash)]
#[serde(tag = "_class", content = "value")]
enum DensityFunctionRepr {
/// Placeholder that leaves space for world-structure contributions at runtime.
// This is a placeholder for leaving space for world structures
Beardifier,
/// Blending alpha factor, initialized from a world seed at runtime.
// These functions are initialized by a seed at runtime
BlendAlpha,
/// Blending offset factor, initialized from a world seed at runtime.
BlendOffset,
/// Blends the density from an inner function.
BlendDensity {
/// The inner density function to blend.
input: Box<Self>,
},
FindTopSurface {
/// The density function to test for solidity.
density: Box<Self>,
/// The density function providing the upper Y bound.
#[serde(rename(deserialize = "upperBound"))]
upper_bound: Box<Self>,
/// Lower bound and step size parameters.
#[serde(flatten)]
data: FindTopSurfaceData,
},
/// End-islands noise sampler, seeded at runtime.
EndIslands,
/// A standard noise sampler.
Noise {
/// Noise parameters (ID and frequency scales).
#[serde(flatten)]
data: NoiseData,
},
/// Horizontal shift noise along the A axis.
ShiftA {
/// Noise ID for the offset generator.
#[serde(rename(deserialize = "offsetNoise"))]
noise_id: String,
},
/// Horizontal shift noise along the B axis.
ShiftB {
/// Noise ID for the offset generator.
#[serde(rename(deserialize = "offsetNoise"))]
noise_id: String,
},
/// A noise sample shifted in XYZ by three inner density functions.
ShiftedNoise {
/// Density function providing the X shift.
#[serde(rename(deserialize = "shiftX"))]
shift_x: Box<Self>,
/// Density function providing the Y shift.
#[serde(rename(deserialize = "shiftY"))]
shift_y: Box<Self>,
/// Density function providing the Z shift.
#[serde(rename(deserialize = "shiftZ"))]
shift_z: Box<Self>,
/// Noise ID and frequency scales for the shifted sample.
#[serde(flatten)]
data: ShiftedNoiseData,
},
/// A trilinearly interpolated multi-octave noise sampler.
InterpolatedNoiseSampler {
/// Sampler configuration parameters.
#[serde(flatten)]
data: InterpolatedNoiseSamplerData,
},
/// Scales an input density function by a cave/tunnel rarity curve.
#[serde(rename(deserialize = "WeirdScaledSampler"))]
WeirdScaled {
/// The density function to scale.
input: Box<Self>,
/// Noise ID and mapper type for scaling.
#[serde(flatten)]
data: WeirdScaledData,
},
/// Wraps an inner function with a caching or interpolation layer.
// The wrapped function is wrapped in a new wrapper at runtime
#[serde(rename(deserialize = "Wrapping"))]
Wrapper {
/// The inner density function to wrap.
#[serde(rename(deserialize = "wrapped"))]
input: Box<Self>,
/// The type of wrapper to apply.
#[serde(rename(deserialize = "type"))]
wrapper: WrapperType,
},
/// Returns a constant density value.
// These functions are unchanged except possibly for internal functions
Constant {
/// The constant output value.
value: HashableF64,
},
/// A linear gradient clamped between two Y levels.
#[serde(rename(deserialize = "YClampedGradient"))]
ClampedYGradient {
/// Gradient parameters.
#[serde(flatten)]
data: ClampedYGradientData,
},
/// Applies a binary operation to two inner density functions.
#[serde(rename(deserialize = "BinaryOperation"))]
Binary {
/// First argument density function.
argument1: Box<Self>,
/// Second argument density function.
argument2: Box<Self>,
/// Operation type and output range metadata.
#[serde(flatten)]
data: BinaryData,
},
/// Applies a linear (add or multiply) operation with a scalar.
#[serde(rename(deserialize = "LinearOperation"))]
Linear {
/// The inner density function to transform.
input: Box<Self>,
/// Operation type, scalar argument, and output range metadata.
#[serde(flatten)]
data: LinearData,
},
/// Applies a unary transformation to an inner density function.
#[serde(rename(deserialize = "UnaryOperation"))]
Unary {
/// The inner density function to transform.
input: Box<Self>,
/// Transformation type and output range metadata.
#[serde(flatten)]
data: UnaryData,
},
/// Clamps an inner density function's output to a range.
Clamp {
/// The inner density function to clamp.
input: Box<Self>,
/// Clamp range parameters.
#[serde(flatten)]
data: ClampData,
},
/// Selects one of two density functions based on whether the input is within a range.
RangeChoice {
/// The density function to evaluate for range testing.
input: Box<Self>,
/// Density function used when `input` is within the range.
#[serde(rename(deserialize = "whenInRange"))]
when_in_range: Box<Self>,
/// Density function used when `input` is outside the range.
#[serde(rename(deserialize = "whenOutOfRange"))]
when_out_range: Box<Self>,
/// Range bounds and output metadata.
#[serde(flatten)]
data: RangeChoiceData,
},
/// Evaluates a cubic spline over a location density function.
Spline {
/// The spline structure.
spline: SplineRepr,
/// Output range metadata.
#[serde(flatten)]
data: SplineData,
},
}
impl DensityFunctionRepr {
/// Computes a stable 64-bit hash for this density function node.
fn unique_id(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Returns the index of this component in `stack`, inserting it if not yet present.
///
/// # Arguments
/// – `stack` – accumulator of all unique density-function component token streams.
/// – `hash_to_index_map` – maps a component's hash to its index in `stack`.
fn get_index_for_component(
&self,
stack: &mut Vec<TokenStream>,
hash_to_index_map: &mut BTreeMap<u64, usize>,
) -> usize {
if let Some(index) = hash_to_index_map.get(&self.unique_id()) {
*index
} else {
let id = self.unique_id();
let repr = self.get_token_stream(stack, hash_to_index_map);
stack.push(repr);
let index = stack.len() - 1;
hash_to_index_map.insert(id, index);
index
}
}
/// Emits the `BaseNoiseFunctionComponent` token stream for this node, registering subcomponents into `stack`.
///
/// # Arguments
/// – `stack` – accumulator of all unique density-function component token streams.
/// – `hash_to_index_map` – maps a component's hash to its index in `stack`.
fn get_token_stream(
&self,
stack: &mut Vec<TokenStream>,
hash_to_index_map: &mut BTreeMap<u64, usize>,
) -> TokenStream {
match self {
Self::Spline { spline, data } => {
let _ = data;
let spline_repr = spline.get_token_stream(stack, hash_to_index_map);
quote! {
BaseNoiseFunctionComponent::Spline {
spline: &#spline_repr,
}
}
}
Self::FindTopSurface {
density,
upper_bound,
data,
} => {
let density_index = density.get_index_for_component(stack, hash_to_index_map);
let upper_bound_index =
upper_bound.get_index_for_component(stack, hash_to_index_map);
let lower_bound = data.lower_bound;
let cell_height = data.cell_height;
quote! {
BaseNoiseFunctionComponent::FindTopSurface {
density_index: #density_index,
upper_bound_index: #upper_bound_index,
data: &FindTopSurfaceData {
lower_bound: #lower_bound,
cell_height: #cell_height,
},
}
}
}
Self::EndIslands => quote! {
BaseNoiseFunctionComponent::EndIslands
},
Self::Noise { data } => {
let noise_id = quote::format_ident!("{}", data.noise_id.to_shouty_snake_case());
let xz_scale = &data.xz_scale;
let y_scale = &data.y_scale;
quote! {
BaseNoiseFunctionComponent::Noise {
data: &NoiseData {
noise_id: DoublePerlinNoiseParameters::#noise_id,
xz_scale: #xz_scale,
y_scale: #y_scale,
}
}
}
}
Self::ShiftA { noise_id } => {
let noise_id = quote::format_ident!("{}", noise_id.to_shouty_snake_case());
quote! {
BaseNoiseFunctionComponent::ShiftA {
noise_id: DoublePerlinNoiseParameters::#noise_id,
}
}
}
Self::ShiftB { noise_id } => {
let noise_id = quote::format_ident!("{}", noise_id.to_shouty_snake_case());
quote! {
BaseNoiseFunctionComponent::ShiftB {
noise_id: DoublePerlinNoiseParameters::#noise_id,
}
}
}
Self::BlendDensity { input } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
quote! {
BaseNoiseFunctionComponent::BlendDensity {
input_index: #input_index,
}
}
}
Self::BlendAlpha => {
quote! {
BaseNoiseFunctionComponent::BlendAlpha
}
}
Self::BlendOffset => {
quote! {
BaseNoiseFunctionComponent::BlendOffset
}
}
Self::Beardifier => {
quote! {
BaseNoiseFunctionComponent::Beardifier
}
}
Self::ShiftedNoise {
shift_x,
shift_y,
shift_z,
data,
} => {
let shift_x_index = shift_x.get_index_for_component(stack, hash_to_index_map);
let shift_y_index = shift_y.get_index_for_component(stack, hash_to_index_map);
let shift_z_index = shift_z.get_index_for_component(stack, hash_to_index_map);
let xz_scale = &data.xz_scale;
let y_scale = &data.y_scale;
let noise_id = quote::format_ident!("{}", data.noise_id.to_shouty_snake_case());
quote! {
BaseNoiseFunctionComponent::ShiftedNoise {
shift_x_index: #shift_x_index,
shift_y_index: #shift_y_index,
shift_z_index: #shift_z_index,
data: &ShiftedNoiseData {
xz_scale: #xz_scale,
y_scale: #y_scale,
noise_id: DoublePerlinNoiseParameters::#noise_id,
},
}
}
}
Self::RangeChoice {
input,
when_in_range,
when_out_range,
data,
} => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let when_in_index = when_in_range.get_index_for_component(stack, hash_to_index_map);
let when_out_index =
when_out_range.get_index_for_component(stack, hash_to_index_map);
let min_inclusive = &data.min_inclusive;
let max_exclusive = &data.max_exclusive;
quote! {
BaseNoiseFunctionComponent::RangeChoice {
input_index: #input_index,
when_in_range_index: #when_in_index,
when_out_range_index: #when_out_index,
data: &RangeChoiceData {
min_inclusive: #min_inclusive,
max_exclusive: #max_exclusive,
},
}
}
}
Self::Binary {
argument1,
argument2,
data,
} => {
let argument1_index = argument1.get_index_for_component(stack, hash_to_index_map);
let argument2_index = argument2.get_index_for_component(stack, hash_to_index_map);
let action = data.operation.get_token_stream();
quote! {
BaseNoiseFunctionComponent::Binary {
argument1_index: #argument1_index,
argument2_index: #argument2_index,
data: &BinaryData {
operation: #action,
},
}
}
}
Self::ClampedYGradient { data } => {
let from_y = f64::from(data.from_y);
let to_y = f64::from(data.to_y);
let from_value = &data.from_value;
let to_value = &data.to_value;
quote! {
BaseNoiseFunctionComponent::ClampedYGradient {
data: &ClampedYGradientData {
from_y: #from_y,
to_y: #to_y,
from_value: #from_value,
to_value: #to_value,
}
}
}
}
Self::Constant { value } => {
quote! {
BaseNoiseFunctionComponent::Constant {
value: #value
}
}
}
Self::Wrapper { input, wrapper } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let wrapper_repr = wrapper.into_token_stream();
quote! {
BaseNoiseFunctionComponent::Wrapper {
input_index: #input_index,
wrapper: #wrapper_repr,
}
}
}
Self::Linear { input, data } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let action = data.operation.into_token_stream();
let argument = &data.argument;
quote! {
BaseNoiseFunctionComponent::Linear {
input_index: #input_index,
data: &LinearData {
operation: #action,
argument: #argument,
},
}
}
}
Self::Clamp { input, data } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let min_value = &data.min_value;
let max_value = &data.max_value;
quote! {
BaseNoiseFunctionComponent::Clamp {
input_index: #input_index,
data: &ClampData {
min_value: #min_value,
max_value: #max_value,
},
}
}
}
Self::Unary { input, data } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let action = data.operation.into_token_stream();
quote! {
BaseNoiseFunctionComponent::Unary {
input_index: #input_index,
data: &UnaryData {
operation: #action,
},
}
}
}
Self::WeirdScaled { input, data } => {
let input_index = input.get_index_for_component(stack, hash_to_index_map);
let noise_id = quote::format_ident!("{}", data.noise_id.to_shouty_snake_case());
let action = data.mapper.into_token_stream();
quote! {
BaseNoiseFunctionComponent::WeirdScaled {
input_index: #input_index,
data: &WeirdScaledData {
noise_id: DoublePerlinNoiseParameters::#noise_id,
mapper: #action,
},
}
}
}
Self::InterpolatedNoiseSampler { data } => {
let scaled_xz_scale = &data.scaled_xz_scale;
let scaled_y_scale = &data.scaled_y_scale;
let xz_factor = &data.xz_factor;
let y_factor = &data.y_factor;
let smear_scale_multiplier = &data.smear_scale_multiplier;
quote! {
BaseNoiseFunctionComponent::InterpolatedNoiseSampler {
data: &InterpolatedNoiseSamplerData {
scaled_xz_scale: #scaled_xz_scale,
scaled_y_scale: #scaled_y_scale,
xz_factor: #xz_factor,
y_factor: #y_factor,
smear_scale_multiplier: #smear_scale_multiplier,
}
}
}
}
}
}
}
/// Top-level container for all dimension noise router representations deserialized from JSON.
#[derive(Deserialize)]
struct NoiseRouterReprs {
/// Standard overworld noise router.
overworld: NoiseRouterRepr,
/// Large-biomes overworld noise router variant.
#[serde(rename(deserialize = "large_biomes"))]
overworld_large_biomes: NoiseRouterRepr,
/// Amplified overworld noise router variant.
#[serde(rename(deserialize = "amplified"))]
overworld_amplified: NoiseRouterRepr,
/// Nether dimension noise router.
nether: NoiseRouterRepr,
/// End dimension noise router.
end: NoiseRouterRepr,
/// Floating-islands (End) noise router variant.
#[serde(rename(deserialize = "floating_islands"))]
end_islands: NoiseRouterRepr,
}
/// Deserialized noise router for a single dimension, containing all density function roots.
#[derive(Deserialize)]
struct NoiseRouterRepr {
/// Density function controlling aquifer barrier generation.
#[serde(rename(deserialize = "barrierNoise"))]
barrier_noise: DensityFunctionRepr,
/// Density function controlling fluid-level floodedness.
#[serde(rename(deserialize = "fluidLevelFloodednessNoise"))]
fluid_level_floodedness_noise: DensityFunctionRepr,
/// Density function controlling how fluid levels spread.
#[serde(rename(deserialize = "fluidLevelSpreadNoise"))]
fluid_level_spread_noise: DensityFunctionRepr,
/// Density function controlling lava pocket generation.
#[serde(rename(deserialize = "lavaNoise"))]
lava_noise: DensityFunctionRepr,
/// Density function for biome temperature noise.
temperature: DensityFunctionRepr,
/// Density function for biome vegetation noise.
vegetation: DensityFunctionRepr,
/// Density function for continental-scale terrain shaping.
continents: DensityFunctionRepr,
/// Density function for erosion-based terrain shaping.
erosion: DensityFunctionRepr,
/// Density function encoding terrain depth below the surface.
depth: DensityFunctionRepr,
/// Density function for terrain ridge shaping.
ridges: DensityFunctionRepr,
/// Preliminary surface density used for above-surface checks (without jaggedness).
#[serde(rename(deserialize = "preliminarySurfaceLevel"))]
preliminary_surface_level: DensityFunctionRepr,
/// Final solid/air density used for block placement.
#[serde(rename(deserialize = "finalDensity"))]
final_density: DensityFunctionRepr,
/// Density function toggling ore-vein generation.
#[serde(rename(deserialize = "veinToggle"))]
vein_toggle: DensityFunctionRepr,
/// Density function for ridged ore-vein shaping.
#[serde(rename(deserialize = "veinRidged"))]
vein_ridged: DensityFunctionRepr,
/// Density function controlling gaps within ore veins.
#[serde(rename(deserialize = "veinGap"))]
vein_gap: DensityFunctionRepr,
}
impl NoiseRouterRepr {
/// Consumes this router representation and emits the `BaseNoiseRouters` token stream.
fn into_token_stream(self) -> TokenStream {
let mut noise_component_stack = Vec::new();
let mut noise_lookup_map = BTreeMap::new();
// The aquifer sampler is called most often
let final_density = self
.final_density
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let barrier_noise = self
.barrier_noise
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let fluid_level_floodedness_noise = self
.fluid_level_floodedness_noise
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let fluid_level_spread_noise = self
.fluid_level_spread_noise
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let lava_noise = self
.lava_noise
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
// Ore sampler is called fewer times than aquifer sampler
let vein_toggle = self
.vein_toggle
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let vein_ridged = self
.vein_ridged
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let vein_gap = self
.vein_gap
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
// These should all be cached so it doesn't matter where their components are
let noise_erosion = self
.erosion
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let noise_depth = self
.depth
.get_index_for_component(&mut noise_component_stack, &mut noise_lookup_map);
let mut surface_component_stack = Vec::new();
let mut surface_lookup_map = BTreeMap::new();
let _ = self
.preliminary_surface_level
.get_index_for_component(&mut surface_component_stack, &mut surface_lookup_map);
let mut multinoise_component_stack = Vec::new();
let mut multinoise_lookup_map = BTreeMap::new();
let ridges = self
.ridges
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
let temperature = self
.temperature
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
let vegetation = self
.vegetation
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
let continents = self
.continents
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
let multi_erosion = self
.erosion
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
let multi_depth = self
.depth
.get_index_for_component(&mut multinoise_component_stack, &mut multinoise_lookup_map);
quote! {
BaseNoiseRouters {
noise: BaseNoiseRouter {
full_component_stack: &[#(#noise_component_stack),*],
barrier_noise: #barrier_noise,
fluid_level_floodedness_noise: #fluid_level_floodedness_noise,
fluid_level_spread_noise: #fluid_level_spread_noise,
lava_noise: #lava_noise,
erosion: #noise_erosion,
depth: #noise_depth,
final_density: #final_density,
vein_toggle: #vein_toggle,
vein_ridged: #vein_ridged,
vein_gap: #vein_gap,
},
surface_estimator: BaseSurfaceEstimator {
full_component_stack: &[#(#surface_component_stack),*],
},
multi_noise: BaseMultiNoiseRouter {
full_component_stack: &[#(#multinoise_component_stack),*],
temperature: #temperature,
vegetation: #vegetation,
continents: #continents,
erosion: #multi_erosion,
depth: #multi_depth,
ridges: #ridges,
},
}
}
}
}
/// Wraps `$router.final_density` in a `Beardifier`-add and `CellCache` wrapper, mirroring the
/// Java runtime mutation applied to aquifer generators.
macro_rules! fix_final_density {
($router:expr) => {{
$router.final_density = DensityFunctionRepr::Wrapper {
input: Box::new(DensityFunctionRepr::Binary {
argument1: Box::new($router.final_density),
argument2: Box::new(DensityFunctionRepr::Beardifier),
data: BinaryData {
operation: BinaryOperation::Add,
max_value: HashableF64(f64::INFINITY),
min_value: HashableF64(f64::NEG_INFINITY),
},
}),
wrapper: WrapperType::CellCache,
};
}};
}
/// Reads `density_function.json` and emits the complete noise-router constants `TokenStream`.
pub fn build() -> TokenStream {
let mut reprs: NoiseRouterReprs =
serde_json5::from_str(&fs::read_to_string("../assets/density_function.json").unwrap())
.expect("could not deserialize density_function.json");
// The `final_density` function is mutated at runtime for the aquifer generator in Java.
fix_final_density!(reprs.overworld);
fix_final_density!(reprs.overworld_amplified);
fix_final_density!(reprs.overworld_large_biomes);
fix_final_density!(reprs.nether);
let _ = reprs.end;
let _ = reprs.end_islands;
let overworld_router = reprs.overworld.into_token_stream();
let nether_router = reprs.nether.into_token_stream();
let end_router = reprs.end.into_token_stream();
quote! {
use crate::chunk::DoublePerlinNoiseParameters;
pub struct NoiseData {
pub noise_id: DoublePerlinNoiseParameters,
pub xz_scale: f64,
pub y_scale: f64,
}
pub struct FindTopSurfaceData {
pub lower_bound: i32,
pub cell_height: i32,
}
pub struct ShiftedNoiseData {
pub xz_scale: f64,
pub y_scale: f64,
pub noise_id: DoublePerlinNoiseParameters,
}
#[derive(Copy, Clone)]
pub enum WeirdScaledMapper {
Caves,
Tunnels,
}
impl WeirdScaledMapper {
#[inline]
#[must_use]
pub const fn max_multiplier(&self) -> f64 {
match self {
Self::Tunnels => 2.0,
Self::Caves => 3.0,
}
}
#[inline]
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn scale(&self, value: f64) -> f64 {
match self {
Self::Tunnels => {
if value < -0.5 {
0.75
} else if value < 0.0 {
1.0
} else if value < 0.5 {
1.5
} else {
2.0
}
}
Self::Caves => {
if value < -0.75 {
0.5
} else if value < -0.5 {
0.75
} else if value < 0.5 {
1.0
} else if value < 0.75 {
2.0
} else {
3.0
}
}
}
}
}
pub struct WeirdScaledData {
pub noise_id: DoublePerlinNoiseParameters,
pub mapper: WeirdScaledMapper,
}
pub struct InterpolatedNoiseSamplerData {
pub scaled_xz_scale: f64,
pub scaled_y_scale: f64,
pub xz_factor: f64,
pub y_factor: f64,
pub smear_scale_multiplier: f64,
}
pub struct ClampedYGradientData {
pub from_y: f64,
pub to_y: f64,
pub from_value: f64,
pub to_value: f64,
}
#[derive(Copy, Clone)]
pub enum BinaryOperation {
Add,
Mul,
Min,
Max,
}
pub struct BinaryData {
pub operation: BinaryOperation,
}
#[derive(Copy, Clone)]
pub enum LinearOperation {
Add,
Mul,
}
pub struct LinearData {
pub operation: LinearOperation,
pub argument: f64,
}
impl LinearData {
#[inline]
#[must_use]
pub const fn apply_density(&self, density: f64) -> f64 {
match self.operation {
LinearOperation::Add => density + self.argument,
LinearOperation::Mul => density * self.argument,
}
}
}
#[derive(Copy, Clone)]
pub enum UnaryOperation {
Abs,
Square,
Cube,
HalfNegative,
QuarterNegative,
Squeeze,
Invert, // new in 26.1
}
pub struct UnaryData {
pub operation: UnaryOperation,
}
impl UnaryData {
#[inline]
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn apply_density(&self, density: f64) -> f64 {
match self.operation {
UnaryOperation::Abs => density.abs(),
UnaryOperation::Square => density * density,
UnaryOperation::Cube => density * density * density,
UnaryOperation::HalfNegative => {
if density > 0.0 {
density
} else {
density * 0.5
}
}
UnaryOperation::QuarterNegative => {
if density > 0.0 {
density
} else {
density * 0.25
}
}
UnaryOperation::Squeeze => {
let clamped = density.clamp(-1.0, 1.0);
clamped / 2.0 - clamped * clamped * clamped / 24.0
}
UnaryOperation::Invert => {
if density == 0.0 { f64::INFINITY } else { 1.0 / density }
},
}
}
}
pub struct ClampData {
pub min_value: f64,
pub max_value: f64,
}
impl ClampData {
#[inline]
#[must_use]
pub const fn apply_density(&self, density: f64) -> f64 {
density.clamp(self.min_value, self.max_value)
}
}
pub struct RangeChoiceData {
pub min_inclusive: f64,
pub max_exclusive: f64,
}
pub struct SplinePoint {
pub location: f32,
pub value: &'static SplineRepr,
pub derivative: f32,
}
pub enum SplineRepr {
Standard {
location_function_index: usize,
points: &'static [SplinePoint],
},
Fixed { value: f32 },
}
#[derive(Copy, Clone)]
pub enum WrapperType {
Interpolated,
CacheFlat,
Cache2D,
CacheOnce,
CellCache,
}
pub enum BaseNoiseFunctionComponent {
// This is a placeholder for leaving space for world structures
Beardifier,
// These functions are initialized by a seed at runtime
BlendAlpha,
BlendOffset,
BlendDensity {
input_index: usize,
},
FindTopSurface {
density_index: usize,
upper_bound_index: usize,
data: &'static FindTopSurfaceData,
},
EndIslands,
Noise {
data: &'static NoiseData,
},
ShiftA {
noise_id: DoublePerlinNoiseParameters,
},
ShiftB {
noise_id: DoublePerlinNoiseParameters,
},
ShiftedNoise {
shift_x_index: usize,
shift_y_index: usize,
shift_z_index: usize,
data: &'static ShiftedNoiseData,
},
InterpolatedNoiseSampler {
data: &'static InterpolatedNoiseSamplerData,
},
WeirdScaled {
input_index: usize,
data: &'static WeirdScaledData,
},
// The wrapped function is wrapped in a new wrapper at runtime
Wrapper {
input_index: usize,
wrapper: WrapperType,
},
// These functions are unchanged except possibly for internal functions
Constant {
value: f64,
},
ClampedYGradient {
data: &'static ClampedYGradientData,
},
Binary {
argument1_index: usize,
argument2_index: usize,
data: &'static BinaryData,
},
Linear {
input_index: usize,
data: &'static LinearData,
},
Unary {
input_index: usize,
data: &'static UnaryData,
},
Clamp {
input_index: usize,
data: &'static ClampData,
},
RangeChoice {
input_index: usize,
when_in_range_index: usize,
when_out_range_index: usize,
data: &'static RangeChoiceData,
},
Spline {
spline: &'static SplineRepr,
},
}
pub struct BaseNoiseRouter {
pub full_component_stack: &'static [BaseNoiseFunctionComponent],
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 BaseSurfaceEstimator {
pub full_component_stack: &'static [BaseNoiseFunctionComponent],
}
pub struct BaseMultiNoiseRouter {
pub full_component_stack: &'static [BaseNoiseFunctionComponent],
pub temperature: usize,
pub vegetation: usize,
pub continents: usize,
pub erosion: usize,
pub depth: usize,
pub ridges: usize,
}
pub struct BaseNoiseRouters {
pub noise: BaseNoiseRouter,
pub surface_estimator: BaseSurfaceEstimator,
pub multi_noise: BaseMultiNoiseRouter,
}
pub const OVERWORLD_BASE_NOISE_ROUTER: BaseNoiseRouters = #overworld_router;
pub const NETHER_BASE_NOISE_ROUTER: BaseNoiseRouters = #nether_router;
pub const END_BASE_NOISE_ROUTER: BaseNoiseRouters = #end_router;
}
}