This commit is contained in:
Alexander Medvedev 2026-04-30 18:06:00 +02:00
parent 1fcdb71da7
commit 6581924760
6 changed files with 729 additions and 6586 deletions

View File

@ -1,395 +1,459 @@
use heck::ToShoutySnakeCase; use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{ToTokens, format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use std::{collections::BTreeMap, fs}; use std::{collections::BTreeMap, fs};
// ── Deserialized input types (unchanged from original) ──────────────────────── /// Deserialized block reference used in chunk generation settings (e.g., default block or fluid).
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct BlockStateCodecStruct { pub struct BlockStateCodecStruct {
/// Block registry name including the `minecraft:` namespace prefix.
#[serde(rename = "Name")] #[serde(rename = "Name")]
pub name: String, pub name: String,
/// Optional block state properties (e.g., `{"facing": "north"}`).
#[serde(rename = "Properties")] #[serde(rename = "Properties")]
pub properties: Option<BTreeMap<String, String>>, pub properties: Option<BTreeMap<String, String>>,
} }
/// Deserialized chunk generation settings for a dimension, sourced from `chunk_gen_settings.json`.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct GenerationSettingsStruct { pub struct GenerationSettingsStruct {
/// Whether aquifer (underground water pocket) generation is enabled.
#[serde(default)] #[serde(default)]
pub aquifers_enabled: bool, pub aquifers_enabled: bool,
/// Whether ore-vein generation is enabled.
#[serde(default)] #[serde(default)]
pub ore_veins_enabled: bool, pub ore_veins_enabled: bool,
/// Whether to use the legacy random number source for this dimension.
#[serde(default)] #[serde(default)]
pub legacy_random_source: bool, pub legacy_random_source: bool,
/// Y-level treated as sea level for this dimension.
pub sea_level: i32, pub sea_level: i32,
/// Default fluid block (usually water or lava) placed by the aquifer generator.
pub default_fluid: BlockStateCodecStruct, pub default_fluid: BlockStateCodecStruct,
/// Default solid block used to fill the terrain.
pub default_block: BlockStateCodecStruct, pub default_block: BlockStateCodecStruct,
/// Noise shape parameters controlling vertical and horizontal cell sizes.
#[serde(rename = "noise")] #[serde(rename = "noise")]
pub shape: GenerationShapeConfigStruct, pub shape: GenerationShapeConfigStruct,
/// Hierarchical surface material rule determining which block is placed at each surface point.
pub surface_rule: MaterialRuleStruct, pub surface_rule: MaterialRuleStruct,
} }
/// Deserialized noise-shape configuration controlling terrain cell dimensions.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct GenerationShapeConfigStruct { pub struct GenerationShapeConfigStruct {
/// Minimum Y level for terrain generation.
pub min_y: i8, pub min_y: i8,
/// Total vertical span of the generation region in blocks.
pub height: u16, pub height: u16,
/// Log₂ of the horizontal cell block count (cell width = `1 << size_horizontal`).
pub size_horizontal: u8, pub size_horizontal: u8,
/// Log₂ of the vertical cell block count (cell height = `1 << size_vertical`).
pub size_vertical: u8, pub size_vertical: u8,
} }
/// Deserialized surface material rule that determines which block to place at a given surface point.
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum MaterialRuleStruct { pub enum MaterialRuleStruct {
/// Place a specific block state unconditionally.
#[serde(rename = "minecraft:block")] #[serde(rename = "minecraft:block")]
Block { result_state: BlockStateCodecStruct }, Block {
/// The block state to place.
result_state: BlockStateCodecStruct,
},
/// Evaluate each rule in order, stopping at the first match.
#[serde(rename = "minecraft:sequence")] #[serde(rename = "minecraft:sequence")]
Sequence { sequence: Vec<Self> }, Sequence {
/// The ordered list of child rules.
sequence: Vec<Self>,
},
/// Apply `then_run` only when `if_true` evaluates to true.
#[serde(rename = "minecraft:condition")] #[serde(rename = "minecraft:condition")]
Condition { Condition {
/// The condition that must be satisfied.
if_true: MaterialConditionStruct, if_true: MaterialConditionStruct,
/// The rule to apply when the condition is met.
then_run: Box<Self>, then_run: Box<Self>,
}, },
/// Special Badlands terrain coloring rule.
#[serde(rename = "minecraft:bandlands")] #[serde(rename = "minecraft:bandlands")]
Badlands, Badlands,
} }
/// Deserialized surface material condition that gates a material rule.
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum MaterialConditionStruct { pub enum MaterialConditionStruct {
/// True when the current position is in one of the listed biomes.
#[serde(rename = "minecraft:biome")] #[serde(rename = "minecraft:biome")]
Biome { biome_is: Vec<String> }, Biome {
/// List of biome resource locations to match against.
biome_is: Vec<String>,
},
/// True when a named noise value is within the given range.
#[serde(rename = "minecraft:noise_threshold")] #[serde(rename = "minecraft:noise_threshold")]
NoiseThreshold { NoiseThreshold {
/// Resource location of the noise parameter.
noise: String, noise: String,
/// Minimum threshold (inclusive).
min_threshold: f64, min_threshold: f64,
/// Maximum threshold (inclusive).
max_threshold: f64, max_threshold: f64,
}, },
/// True below a Y offset and false above another, with a linear gradient in between.
#[serde(rename = "minecraft:vertical_gradient")] #[serde(rename = "minecraft:vertical_gradient")]
VerticalGradient { VerticalGradient {
/// Name of the random source used for this gradient.
random_name: String, random_name: String,
/// Y offset below which the condition is always true.
true_at_and_below: YOffsetStruct, true_at_and_below: YOffsetStruct,
/// Y offset above which the condition is always false.
false_at_and_above: YOffsetStruct, false_at_and_above: YOffsetStruct,
}, },
/// True when the position is above a given Y anchor.
#[serde(rename = "minecraft:y_above")] #[serde(rename = "minecraft:y_above")]
YAbove { YAbove {
/// The Y offset anchor to compare against.
anchor: YOffsetStruct, anchor: YOffsetStruct,
/// Multiplier applied to surface depth when computing the threshold.
surface_depth_multiplier: i32, surface_depth_multiplier: i32,
/// Whether to add stone depth to the comparison value.
add_stone_depth: bool, add_stone_depth: bool,
}, },
/// True when the position is above a water surface within a certain offset.
#[serde(rename = "minecraft:water")] #[serde(rename = "minecraft:water")]
Water { Water {
/// Y offset relative to the water surface.
offset: i32, offset: i32,
/// Multiplier applied to surface depth.
surface_depth_multiplier: i32, surface_depth_multiplier: i32,
/// Whether to add stone depth to the comparison value.
add_stone_depth: bool, add_stone_depth: bool,
}, },
/// True when the biome temperature is cold (below freezing).
#[serde(rename = "minecraft:temperature")] #[serde(rename = "minecraft:temperature")]
Temperature, Temperature,
/// True when the terrain is steep (high slope).
#[serde(rename = "minecraft:steep")] #[serde(rename = "minecraft:steep")]
Steep, Steep,
/// Inverts the inner condition.
#[serde(rename = "minecraft:not")] #[serde(rename = "minecraft:not")]
Not { invert: Box<Self> }, Not {
/// The condition to invert.
invert: Box<Self>,
},
/// True when there is a hole (cave opening) at the position.
#[serde(rename = "minecraft:hole")] #[serde(rename = "minecraft:hole")]
Hole, Hole,
/// True when the position is above the preliminary surface estimate.
#[serde(rename = "minecraft:above_preliminary_surface")] #[serde(rename = "minecraft:above_preliminary_surface")]
AbovePreliminarySurface, AbovePreliminarySurface,
/// True based on the depth of stone/ceiling relative to the surface.
#[serde(rename = "minecraft:stone_depth")] #[serde(rename = "minecraft:stone_depth")]
StoneDepth { StoneDepth {
/// Y offset added to the depth value.
offset: i32, offset: i32,
/// Whether to include surface depth in the calculation.
add_surface_depth: bool, add_surface_depth: bool,
/// Additional secondary depth range.
secondary_depth_range: i32, secondary_depth_range: i32,
/// Surface type to measure from: `"ceiling"` or `"floor"`.
surface_type: String, surface_type: String,
}, },
} }
/// Deserialized Y offset that can be expressed relative to different reference points.
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum YOffsetStruct { pub enum YOffsetStruct {
Absolute { absolute: i16 }, /// An absolute Y coordinate.
AboveBottom { above_bottom: i8 }, Absolute {
BelowTop { below_top: i8 }, /// The absolute Y level.
absolute: i16,
},
/// A Y level measured upward from the dimension's minimum Y.
AboveBottom {
/// Number of blocks above the bottom of the dimension.
above_bottom: i8,
},
/// A Y level measured downward from the dimension's maximum Y.
BelowTop {
/// Number of blocks below the top of the dimension.
below_top: i8,
},
} }
// ── Flat bytecode compiler ──────────────────────────────────────────────────── // --- ToTokens Implementations ---
/// Compiles a `MaterialRuleStruct` tree into a flat Vec of `TokenStream`s, impl ToTokens for BlockStateCodecStruct {
/// each representing one `SurfaceInstruction` literal. /// Emits a `BlockBlueprint` literal, stripping the `minecraft:` namespace prefix from the block name.
/// fn to_tokens(&self, tokens: &mut TokenStream) {
/// Layout rules: let name = &self.name.strip_prefix("minecraft:").unwrap_or(&self.name);
/// - `Sequence` → children in order, no instruction of its own let name_stripped = name.strip_prefix("minecraft:").unwrap_or(name);
/// - `Condition` → `Test*` instruction (with a `skip` field) followed by the let block_ident =
/// body instructions; skip jumps past the body on failure quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
/// - `Block` → `PlaceBlock` terminal // TODO: use props
/// - `Badlands` → `PlaceBadlands` terminal tokens.extend(quote! {
struct Compiler { crate::Block::#block_ident.default_state
out: Vec<TokenStream>, });
}
impl Compiler {
fn new() -> Self {
Self { out: Vec::new() }
}
/// Appends instructions for `rule` and returns the number of instructions emitted.
fn compile_rule(&mut self, rule: &MaterialRuleStruct) -> usize {
let before = self.out.len();
match rule {
MaterialRuleStruct::Block { result_state } => {
let ts = block_state_tokens(result_state);
self.out
.push(quote!(SurfaceInstruction::PlaceBlock { state: #ts }));
}
MaterialRuleStruct::Badlands => {
self.out.push(quote!(SurfaceInstruction::PlaceBadlands));
}
MaterialRuleStruct::Sequence { sequence } => {
// Sequences have no instruction of their own; children are inline
for child in sequence {
self.compile_rule(child);
}
}
MaterialRuleStruct::Condition { if_true, then_run } => {
// Reserve a slot for the Test instruction; we need body size first
let test_slot = self.out.len();
self.out.push(TokenStream::new()); // placeholder
let body_len = self.compile_rule(then_run);
let skip = body_len as u16;
// Patch placeholder with the real instruction now that skip is known
self.out[test_slot] = compile_condition(if_true, skip);
}
}
self.out.len() - before
} }
} }
/// Emits a `SurfaceInstruction::Test*` token stream for the given condition. impl ToTokens for GenerationSettingsStruct {
/// `skip` is the number of instructions to jump past on failure. /// Emits a `GenerationSettings` struct literal with all fields populated from the deserialized data.
fn compile_condition(cond: &MaterialConditionStruct, skip: u16) -> TokenStream { fn to_tokens(&self, tokens: &mut TokenStream) {
match cond { let aquifers = self.aquifers_enabled;
MaterialConditionStruct::Biome { biome_is } => { let ores = self.ore_veins_enabled;
let refs: Vec<TokenStream> = biome_is let legacy = self.legacy_random_source;
let sea_level = self.sea_level;
let fluid = &self.default_fluid;
let block = &self.default_block;
let shape = &self.shape;
let rule = &self.surface_rule;
tokens.extend(quote!(
GenerationSettings {
aquifers_enabled: #aquifers,
ore_veins_enabled: #ores,
legacy_random_source: #legacy,
sea_level: #sea_level,
default_fluid: #fluid,
shape: #shape,
surface_rule: #rule,
default_block: #block,
}
));
}
}
impl ToTokens for GenerationShapeConfigStruct {
/// Emits a `GenerationShapeConfig` struct literal with all noise-shape dimensions.
fn to_tokens(&self, tokens: &mut TokenStream) {
let min_y = self.min_y;
let height = self.height;
let hor = self.size_horizontal;
let ver = self.size_vertical;
tokens.extend(quote!(
GenerationShapeConfig { min_y: #min_y, height: #height, size_horizontal: #hor, size_vertical: #ver }
));
}
}
impl ToTokens for YOffsetStruct {
/// Emits a `YOffset` enum variant literal corresponding to the deserialized offset kind.
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Absolute { absolute } => {
tokens.extend(quote!(YOffset::Absolute(pumpkin_util::y_offset::Absolute { absolute: #absolute })));
}
Self::AboveBottom { above_bottom } => {
tokens.extend(quote!(YOffset::AboveBottom(pumpkin_util::y_offset::AboveBottom { above_bottom: #above_bottom })));
}
Self::BelowTop { below_top } => {
tokens.extend(quote!(YOffset::BelowTop(pumpkin_util::y_offset::BelowTop { below_top: #below_top })));
}
}
}
}
impl ToTokens for MaterialConditionStruct {
/// Emits a `MaterialCondition` enum variant literal for each surface condition kind.
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Biome { biome_is } => {
let biomes = biome_is
.iter() .iter()
.map(|b| b.strip_prefix("minecraft:").unwrap_or(b).to_uppercase());
let biome_refs: Vec<TokenStream> = biomes
.map(|b| { .map(|b| {
let ident = format_ident!( let ident = format_ident!("{}", b);
"{}",
b.strip_prefix("minecraft:").unwrap_or(b).to_uppercase()
);
quote!(&crate::biome::Biome::#ident) quote!(&crate::biome::Biome::#ident)
}) })
.collect(); .collect();
quote!(SurfaceInstruction::TestBiome {
biome_is: &[#(#refs),*],
skip: #skip,
})
}
MaterialConditionStruct::NoiseThreshold { tokens.extend(quote!(
MaterialCondition::Biome(BiomeMaterialCondition {
biome_is: &[#(#biome_refs),*],
})
));
}
Self::NoiseThreshold {
noise, noise,
min_threshold, min_threshold,
max_threshold, max_threshold,
} => { } => {
let noise_id = format_ident!( let noise_id = quote::format_ident!(
"{}", "{}",
noise noise
.strip_prefix("minecraft:") .strip_prefix("minecraft:")
.unwrap() .unwrap()
.to_shouty_snake_case() .to_shouty_snake_case()
); );
// f64::MAX (or near it) means "no upper bound" — emit the cheaper variant
if *max_threshold >= f64::MAX / 2.0 {
quote!(SurfaceInstruction::TestNoiseAbove {
noise: DoublePerlinNoiseParameters::#noise_id,
min: #min_threshold,
skip: #skip,
})
} else {
quote!(SurfaceInstruction::TestNoiseRange {
noise: DoublePerlinNoiseParameters::#noise_id,
min: #min_threshold,
max: #max_threshold,
skip: #skip,
})
}
}
MaterialConditionStruct::VerticalGradient { tokens.extend(quote!(
MaterialCondition::NoiseThreshold(NoiseThresholdMaterialCondition {
noise: DoublePerlinNoiseParameters::#noise_id,
min_threshold: #min_threshold,
max_threshold: #max_threshold,
})
));
}
Self::VerticalGradient {
random_name, random_name,
true_at_and_below, true_at_and_below,
false_at_and_above, false_at_and_above,
} => { } => {
// Pre calc for speed :D
let bytes = md5::compute(random_name.as_bytes()); let bytes = md5::compute(random_name.as_bytes());
let lo = u64::from_be_bytes(bytes[0..8].try_into().expect("md5 slice")); let lo = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length"));
let hi = u64::from_be_bytes(bytes[8..16].try_into().expect("md5 slice")); let hi = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length"));
let below = y_offset_tokens(true_at_and_below); tokens.extend(quote!(
let above = y_offset_tokens(false_at_and_above); MaterialCondition::VerticalGradient(VerticalGradientMaterialCondition {
quote!(SurfaceInstruction::TestVerticalGradient {
random_lo: #lo, random_lo: #lo,
random_hi: #hi, random_hi: #hi,
true_at_and_below: #below, true_at_and_below: #true_at_and_below,
false_at_and_above: #above, false_at_and_above: #false_at_and_above,
skip: #skip,
}) })
));
} }
Self::YAbove {
MaterialConditionStruct::YAbove {
anchor, anchor,
surface_depth_multiplier, surface_depth_multiplier,
add_stone_depth, add_stone_depth,
} => { } => {
let anchor_ts = y_offset_tokens(anchor); tokens.extend(quote!(
quote!(SurfaceInstruction::TestYAbove { MaterialCondition::YAbove(AboveYMaterialCondition {
anchor: #anchor_ts, anchor: #anchor,
surface_depth_multiplier: #surface_depth_multiplier, surface_depth_multiplier: #surface_depth_multiplier,
add_stone_depth: #add_stone_depth, add_stone_depth: #add_stone_depth,
skip: #skip,
}) })
));
} }
Self::Water {
MaterialConditionStruct::Water {
offset, offset,
surface_depth_multiplier, surface_depth_multiplier,
add_stone_depth, add_stone_depth,
} => { } => {
quote!(SurfaceInstruction::TestWater { tokens.extend(quote!(
MaterialCondition::Water(WaterMaterialCondition {
offset: #offset, offset: #offset,
surface_depth_multiplier: #surface_depth_multiplier, surface_depth_multiplier: #surface_depth_multiplier,
add_stone_depth: #add_stone_depth, add_stone_depth: #add_stone_depth,
skip: #skip,
}) })
));
} }
Self::Temperature => {
MaterialConditionStruct::StoneDepth { tokens.extend(quote!(MaterialCondition::Temperature));
}
Self::Steep => {
tokens.extend(quote!(MaterialCondition::Steep));
}
Self::Not { invert } => {
tokens.extend(quote!(
MaterialCondition::Not(NotMaterialCondition {
invert: &#invert,
})
));
}
Self::Hole => {
tokens.extend(quote!(MaterialCondition::Hole(HoleMaterialCondition)));
}
Self::AbovePreliminarySurface => {
tokens.extend(quote!(MaterialCondition::AbovePreliminarySurface(
SurfaceMaterialCondition
)));
}
Self::StoneDepth {
offset, offset,
add_surface_depth, add_surface_depth,
secondary_depth_range, secondary_depth_range,
surface_type, surface_type,
} => { } => {
let st = match surface_type.as_str() { let surface_type_token = match surface_type.as_str() {
"ceiling" => quote!(VerticalSurfaceType::Ceiling), "ceiling" => quote!(
_ => quote!(VerticalSurfaceType::Floor), pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Ceiling
),
"floor" => quote!(
pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Floor
),
_ => quote!(panic!("Unknown surface type")),
}; };
quote!(SurfaceInstruction::TestStoneDepth {
tokens.extend(quote!(
MaterialCondition::StoneDepth(StoneDepthMaterialCondition {
offset: #offset, offset: #offset,
add_surface_depth: #add_surface_depth, add_surface_depth: #add_surface_depth,
secondary_depth_range: #secondary_depth_range, secondary_depth_range: #secondary_depth_range,
surface_type: #st, surface_type: #surface_type_token,
skip: #skip,
}) })
));
}
}
}
} }
MaterialConditionStruct::Not { invert } => { impl ToTokens for MaterialRuleStruct {
// Compile the inner condition with skip=0; the Not wrapper flips the result /// Emits a `MaterialRule` enum variant literal for each surface placement rule kind.
let inner = compile_condition(invert, 0); fn to_tokens(&self, tokens: &mut TokenStream) {
quote!(SurfaceInstruction::TestNot { match self {
inner: &#inner, Self::Block { result_state } => {
skip: #skip, tokens.extend(quote!(
MaterialRule::Block(BlockMaterialRule {
result_state: #result_state
}) })
));
} }
Self::Sequence { sequence } => {
MaterialConditionStruct::AbovePreliminarySurface => { tokens.extend(quote!(
quote!(SurfaceInstruction::TestAbovePreliminarySurface { skip: #skip }) MaterialRule::Sequence(SequenceMaterialRule {
} sequence: &[#(#sequence),*]
MaterialConditionStruct::Hole => {
quote!(SurfaceInstruction::TestHole { skip: #skip })
}
MaterialConditionStruct::Steep => {
quote!(SurfaceInstruction::TestSteep { skip: #skip })
}
MaterialConditionStruct::Temperature => {
quote!(SurfaceInstruction::TestTemperature { skip: #skip })
}
}
}
// ── Small token helpers ───────────────────────────────────────────────────────
fn block_state_tokens(b: &BlockStateCodecStruct) -> TokenStream {
let name = b.name.strip_prefix("minecraft:").unwrap_or(&b.name);
let ident = format_ident!("{}", name.to_uppercase().replace([':', '-'], "_"));
quote!(crate::Block::#ident.default_state)
}
fn y_offset_tokens(y: &YOffsetStruct) -> TokenStream {
match y {
YOffsetStruct::Absolute { absolute } => {
quote!(YOffset::Absolute(pumpkin_util::y_offset::Absolute { absolute: #absolute }))
}
YOffsetStruct::AboveBottom { above_bottom } => {
quote!(YOffset::AboveBottom(pumpkin_util::y_offset::AboveBottom { above_bottom: #above_bottom }))
}
YOffsetStruct::BelowTop { below_top } => {
quote!(YOffset::BelowTop(pumpkin_util::y_offset::BelowTop { below_top: #below_top }))
}
}
}
fn shape_tokens(s: &GenerationShapeConfigStruct) -> TokenStream {
let min_y = s.min_y;
let height = s.height;
let hor = s.size_horizontal;
let ver = s.size_vertical;
quote!(GenerationShapeConfig {
min_y: #min_y,
height: #height,
size_horizontal: #hor,
size_vertical: #ver,
}) })
));
} }
Self::Condition { if_true, then_run } => {
fn settings_tokens(s: &GenerationSettingsStruct) -> TokenStream { tokens.extend(quote!(
let aquifers = s.aquifers_enabled; MaterialRule::Condition(ConditionMaterialRule {
let ores = s.ore_veins_enabled; if_true: #if_true,
let legacy = s.legacy_random_source; then_run: &#then_run
let sea_level = s.sea_level;
let fluid = block_state_tokens(&s.default_fluid);
let block = block_state_tokens(&s.default_block);
let shape = shape_tokens(&s.shape);
let mut compiler = Compiler::new();
compiler.compile_rule(&s.surface_rule);
let instructions = &compiler.out;
quote!(GenerationSettings {
aquifers_enabled: #aquifers,
ore_veins_enabled: #ores,
legacy_random_source: #legacy,
sea_level: #sea_level,
default_fluid: #fluid,
default_block: #block,
shape: #shape,
surface_rule: CompiledSurfaceRule {
instructions: &[#(#instructions),*],
},
}) })
));
}
Self::Badlands => {
tokens.extend(quote!(MaterialRule::Badlands(BadLandsMaterialRule)));
}
}
}
} }
// ── Entry point ─────────────────────────────────────────────────────────────── /// Reads `chunk_gen_settings.json` and emits the complete chunk generation settings `TokenStream`.
pub fn build() -> TokenStream { pub fn build() -> TokenStream {
let json: BTreeMap<String, GenerationSettingsStruct> = let json: BTreeMap<String, GenerationSettingsStruct> =
serde_json::from_str(&fs::read_to_string("../assets/chunk_gen_settings.json").unwrap()) serde_json::from_str(&fs::read_to_string("../assets/chunk_gen_settings.json").unwrap())
.expect("Failed to parse chunk_gen_settings.json"); .expect("Failed to parse settings.json");
let const_defs: TokenStream = json let mut const_defs = TokenStream::new();
.iter()
.map(|(name, settings)| { for (name, settings) in &json {
let const_name = format_ident!("{}", name.to_uppercase()); let upper_name = name.to_uppercase();
let body = settings_tokens(settings); let const_name = format_ident!("{}", upper_name);
quote!(pub const #const_name: GenerationSettings = #body;)
}) const_defs.extend(quote!(
.collect(); pub const #const_name: GenerationSettings = #settings;
));
}
quote!( quote!(
use crate::dimension::Dimension; use crate::dimension::Dimension;
use crate::chunk::DoublePerlinNoiseParameters; use crate::chunk::DoublePerlinNoiseParameters;
use crate::BlockState; use crate::BlockState;
use pumpkin_util::y_offset::YOffset;
use pumpkin_util::math::vertical_surface_type::VerticalSurfaceType;
use crate::biome::Biome;
// ── Core settings struct ────────────────────────────────────────────── use std::{cell::RefCell, num::NonZeroUsize};
use pumpkin_util::random::RandomDeriver;
use pumpkin_util::y_offset::YOffset;
use crate::biome::Biome;
use pumpkin_util::y_offset::Absolute;
pub struct GenerationSettings { pub struct GenerationSettings {
pub aquifers_enabled: bool, pub aquifers_enabled: bool,
@ -397,10 +461,9 @@ pub fn build() -> TokenStream {
pub legacy_random_source: bool, pub legacy_random_source: bool,
pub sea_level: i32, pub sea_level: i32,
pub default_fluid: &'static BlockState, pub default_fluid: &'static BlockState,
pub default_block: &'static BlockState,
pub shape: GenerationShapeConfig, pub shape: GenerationShapeConfig,
/// Flat compiled surface rule — no recursion at runtime. pub surface_rule: MaterialRule,
pub surface_rule: CompiledSurfaceRule, pub default_block: &'static BlockState,
} }
pub struct GenerationShapeConfig { pub struct GenerationShapeConfig {
@ -411,9 +474,12 @@ pub fn build() -> TokenStream {
} }
impl GenerationShapeConfig { impl GenerationShapeConfig {
#[inline] #[must_use] #[inline]
#[must_use]
pub const fn vertical_cell_block_count(&self) -> u8 { self.size_vertical << 2 } pub const fn vertical_cell_block_count(&self) -> u8 { self.size_vertical << 2 }
#[inline] #[must_use]
#[inline]
#[must_use]
pub const fn horizontal_cell_block_count(&self) -> u8 { self.size_horizontal << 2 } pub const fn horizontal_cell_block_count(&self) -> u8 { self.size_horizontal << 2 }
#[must_use] #[must_use]
@ -438,148 +504,107 @@ pub fn build() -> TokenStream {
} else { } else {
new_top + new_min.unsigned_abs() as u16 new_top + new_min.unsigned_abs() as u16
}; };
Self { min_y: new_min, height: new_height,
size_horizontal: self.size_horizontal, size_vertical: self.size_vertical }
}
}
// ── Flat bytecode types ───────────────────────────────────────────────
/// A compiled, flat representation of a surface rule tree.
/// Evaluated by a simple index-advancing loop — no recursion.
pub struct CompiledSurfaceRule {
pub instructions: &'static [SurfaceInstruction],
}
/// One instruction in the flat surface rule bytecode.
///
/// Test instructions carry a `skip: u16` field. On failure the evaluator
/// advances the program counter by `skip + 1` (past the body). On success
/// it advances by 1 (into the body). Terminals end evaluation immediately.
pub enum SurfaceInstruction {
// ── Terminals ────────────────────────────────────────────────────
PlaceBlock { state: &'static BlockState },
PlaceBadlands,
// ── Conditions ───────────────────────────────────────────────────
TestBiome {
biome_is: &'static [&'static Biome],
skip: u16,
},
/// Noise >= min (the f64::MAX upper-bound fast path)
TestNoiseAbove {
noise: DoublePerlinNoiseParameters,
min: f64,
skip: u16,
},
/// min <= noise <= max
TestNoiseRange {
noise: DoublePerlinNoiseParameters,
min: f64,
max: f64,
skip: u16,
},
TestVerticalGradient {
random_lo: u64,
random_hi: u64,
true_at_and_below: YOffset,
false_at_and_above: YOffset,
skip: u16,
},
TestYAbove {
anchor: YOffset,
surface_depth_multiplier: i32,
add_stone_depth: bool,
skip: u16,
},
TestWater {
offset: i32,
surface_depth_multiplier: i32,
add_stone_depth: bool,
skip: u16,
},
TestStoneDepth {
offset: i32,
add_surface_depth: bool,
secondary_depth_range: i32,
surface_type: VerticalSurfaceType,
skip: u16,
},
TestAbovePreliminarySurface { skip: u16 },
TestHole { skip: u16 },
TestSteep { skip: u16 },
TestTemperature { skip: u16 },
/// Inverts a single inner condition.
/// The inner condition is stored inline — it must not itself contain
/// a body (i.e. it always has inner skip = 0).
TestNot {
inner: &'static SurfaceInstruction,
skip: u16,
},
}
// ── Per-column noise cache ────────────────────────────────────────────
/// Caches noise samples for one (x, z) column.
/// Call `invalidate(x, z)` once per column; `get` then returns the cached
/// value on subsequent calls for the same noise parameter.
pub struct ColumnNoiseCache {
values: [f64; DoublePerlinNoiseParameters::COUNT],
valid: [bool; DoublePerlinNoiseParameters::COUNT],
col_x: i32,
col_z: i32,
}
impl ColumnNoiseCache {
pub const fn new() -> Self {
Self { Self {
values: [0.0; DoublePerlinNoiseParameters::COUNT], min_y: new_min,
valid: [false; DoublePerlinNoiseParameters::COUNT], height: new_height,
col_x: i32::MIN, size_horizontal: self.size_horizontal,
col_z: i32::MIN, size_vertical: self.size_vertical,
}
} }
} }
/// Must be called at the start of each new (x, z) column. pub struct BlockMaterialRule {
#[inline] pub result_state: &'static BlockState,
pub fn invalidate(&mut self, x: i32, z: i32) {
if self.col_x != x || self.col_z != z {
self.valid = [false; DoublePerlinNoiseParameters::COUNT];
self.col_x = x;
self.col_z = z;
}
} }
/// Returns the cached noise value, sampling it on first access. pub struct SequenceMaterialRule {
#[inline] pub sequence: &'static [MaterialRule],
pub fn get(
&mut self,
noise: &DoublePerlinNoiseParameters,
sample: impl FnOnce() -> f64,
) -> f64 {
let idx = noise.id;
if !self.valid[idx] {
self.values[idx] = sample();
self.valid[idx] = true;
}
self.values[idx]
}
} }
impl Default for ColumnNoiseCache { pub struct ConditionMaterialRule {
fn default() -> Self { Self::new() } pub if_true: MaterialCondition,
pub then_run: &'static MaterialRule,
} }
// ── Generated constants ─────────────────────────────────────────────── pub struct BadLandsMaterialRule;
pub enum MaterialRule {
Block(BlockMaterialRule),
Sequence(SequenceMaterialRule),
Condition(ConditionMaterialRule),
Badlands(BadLandsMaterialRule),
}
pub struct BiomeMaterialCondition {
pub biome_is: &'static [&'static Biome],
}
pub struct NoiseThresholdMaterialCondition {
pub noise: DoublePerlinNoiseParameters,
pub min_threshold: f64,
pub max_threshold: f64,
}
pub struct VerticalGradientMaterialCondition {
pub random_lo: u64,
pub random_hi: u64,
pub true_at_and_below: YOffset,
pub false_at_and_above: YOffset,
}
pub struct AboveYMaterialCondition {
pub anchor: YOffset,
pub surface_depth_multiplier: i32,
pub add_stone_depth: bool,
}
pub struct WaterMaterialCondition {
pub offset: i32,
pub surface_depth_multiplier: i32,
pub add_stone_depth: bool,
}
pub struct HoleMaterialCondition;
pub struct NotMaterialCondition {
pub invert: &'static MaterialCondition,
}
pub struct SurfaceMaterialCondition;
pub struct StoneDepthMaterialCondition {
pub offset: i32,
pub add_surface_depth: bool,
pub secondary_depth_range: i32,
pub surface_type: pumpkin_util::math::vertical_surface_type::VerticalSurfaceType,
}
pub enum MaterialCondition {
Biome(BiomeMaterialCondition),
NoiseThreshold(NoiseThresholdMaterialCondition),
VerticalGradient(VerticalGradientMaterialCondition),
YAbove(AboveYMaterialCondition),
Water(WaterMaterialCondition),
Temperature,
Steep,
Not(NotMaterialCondition),
Hole(HoleMaterialCondition),
AbovePreliminarySurface(SurfaceMaterialCondition),
StoneDepth(StoneDepthMaterialCondition),
}
impl GenerationSettings { impl GenerationSettings {
#const_defs #const_defs
pub fn from_dimension(dimension: &Dimension) -> &'static Self { pub fn from_dimension(dimension: &Dimension) -> &'static Self {
match dimension { if dimension == &Dimension::OVERWORLD {
d if d == &Dimension::OVERWORLD => &Self::OVERWORLD, &Self::OVERWORLD
d if d == &Dimension::THE_NETHER => &Self::NETHER, } else if dimension == &Dimension::THE_NETHER {
_ => &Self::END, &Self::NETHER
} else {
&Self::END
} }
} }
} }

View File

@ -1033,9 +1033,8 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream {
} }
"minecraft:upwards_branching_trunk_placer" => { "minecraft:upwards_branching_trunk_placer" => {
let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]); let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]);
let place_branch_per_log_probability = v["place_branch_per_log_probability"] let place_branch_per_log_probability =
.as_f64() v["place_branch_per_log_probability"].as_f64().unwrap_or(0.0) as f32;
.unwrap_or(0.0) as f32;
let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]); let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]);
let can_grow_through = value_to_block_list(&v["can_grow_through"]); let can_grow_through = value_to_block_list(&v["can_grow_through"]);
quote! { quote! {
@ -1053,8 +1052,7 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream {
let branch_start_offset_v = &v["branch_start_offset_from_top"]; let branch_start_offset_v = &v["branch_start_offset_from_top"];
let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32; let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32;
let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32; let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32;
let branch_end_offset_from_top = let branch_end_offset_from_top = value_to_int_provider(&v["branch_end_offset_from_top"]);
value_to_int_provider(&v["branch_end_offset_from_top"]);
quote! { quote! {
TrunkType::Cherry(CherryTrunkPlacer { TrunkType::Cherry(CherryTrunkPlacer {
branch_count: #branch_count, branch_count: #branch_count,

File diff suppressed because one or more lines are too long

View File

@ -48,7 +48,7 @@ use crate::generation::structure::structures::{
StructureGeneratorContext, StructureInstance, create_chunk_random, StructureGeneratorContext, StructureInstance, create_chunk_random,
}; };
use crate::generation::structure::try_generate_structure; use crate::generation::structure::try_generate_structure;
use crate::generation::surface::evaluate_surface_rule; use crate::generation::surface::rule::try_apply_material_rule;
use crate::{ use crate::{
BlockStateId, BlockStateId,
block::RawBlockState, block::RawBlockState,
@ -879,7 +879,7 @@ impl ProtoChunk {
context.block_pos_y, context.block_pos_y,
context.block_pos_z, context.block_pos_z,
); );
let new_state = evaluate_surface_rule( let new_state = try_apply_material_rule(
&settings.surface_rule, &settings.surface_rule,
self, self,
&mut context, &mut context,

View File

@ -1,12 +1,14 @@
use pumpkin_data::{ use pumpkin_data::{
BlockState, chunk::Biome,
chunk::{Biome, DoublePerlinNoiseParameters}, chunk_gen_settings::{
chunk_gen_settings::{ColumnNoiseCache, CompiledSurfaceRule, SurfaceInstruction}, AboveYMaterialCondition, MaterialCondition, NoiseThresholdMaterialCondition,
NotMaterialCondition, StoneDepthMaterialCondition, VerticalGradientMaterialCondition,
WaterMaterialCondition,
},
}; };
use pumpkin_util::{ use pumpkin_util::{
math::{lerp2, vertical_surface_type::VerticalSurfaceType}, math::{lerp2, vertical_surface_type::VerticalSurfaceType},
random::{RandomImpl, xoroshiro128::XoroshiroSplitter}, random::{RandomImpl, xoroshiro128::XoroshiroSplitter},
y_offset::YOffset,
}; };
use terrain::SurfaceTerrainBuilder; use terrain::SurfaceTerrainBuilder;
@ -24,6 +26,7 @@ use super::{
}, },
}; };
pub mod rule;
pub mod terrain; pub mod terrain;
pub struct MaterialRuleContext<'a> { pub struct MaterialRuleContext<'a> {
@ -49,7 +52,6 @@ pub struct MaterialRuleContext<'a> {
pub stone_depth_above: i32, pub stone_depth_above: i32,
pub terrain_builder: &'a SurfaceTerrainBuilder, pub terrain_builder: &'a SurfaceTerrainBuilder,
pub sea_level: i32, pub sea_level: i32,
pub noise_cache: ColumnNoiseCache,
} }
impl<'a> MaterialRuleContext<'a> { impl<'a> MaterialRuleContext<'a> {
@ -86,7 +88,6 @@ impl<'a> MaterialRuleContext<'a> {
stone_depth_below: 0, stone_depth_below: 0,
stone_depth_above: 0, stone_depth_above: 0,
sea_level, sea_level,
noise_cache: ColumnNoiseCache::new(),
} }
} }
@ -134,234 +135,111 @@ impl<'a> MaterialRuleContext<'a> {
} }
} }
pub fn evaluate_surface_rule( #[expect(clippy::similar_names)]
rule: &CompiledSurfaceRule, pub fn test_condition(
condition: &MaterialCondition,
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,
ctx: &mut MaterialRuleContext, context: &mut MaterialRuleContext,
sampler: &mut SurfaceHeightEstimateSampler, surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
let instrs = &rule.instructions;
let mut pc = 0usize;
let x = ctx.block_pos_x;
let z = ctx.block_pos_z;
let deriver = ctx.random_deriver;
while pc < instrs.len() {
match &instrs[pc] {
SurfaceInstruction::PlaceBlock { state } => return Some(state),
SurfaceInstruction::PlaceBadlands => {
return Some(get_badlands_block(ctx));
}
SurfaceInstruction::TestBiome { biome_is, skip } => {
if !biome_is.iter().any(|b| std::ptr::eq(*b, ctx.biome)) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestNoiseAbove { noise, min, skip } => {
let v = ctx
.noise_cache
.get(noise, || sample_noise(noise, deriver, x, z));
if v < *min {
pc += *skip as usize;
}
}
SurfaceInstruction::TestNoiseRange {
noise,
min,
max,
skip,
} => {
let v = ctx
.noise_cache
.get(noise, || sample_noise(noise, deriver, x, z));
if v < *min || v > *max {
pc += *skip as usize;
}
}
SurfaceInstruction::TestVerticalGradient {
random_lo,
random_hi,
true_at_and_below,
false_at_and_above,
skip,
} => {
if !test_vertical_gradient(
ctx,
*random_lo,
*random_hi,
true_at_and_below,
false_at_and_above,
) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestYAbove {
anchor,
surface_depth_multiplier,
add_stone_depth,
skip,
} => {
if !test_y_above(ctx, anchor, *surface_depth_multiplier, *add_stone_depth) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestWater {
offset,
surface_depth_multiplier,
add_stone_depth,
skip,
} => {
if !test_water(ctx, *offset, *surface_depth_multiplier, *add_stone_depth) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestStoneDepth {
offset,
add_surface_depth,
secondary_depth_range,
surface_type,
skip,
} => {
if !test_stone_depth(
ctx,
*offset,
*add_surface_depth,
*secondary_depth_range,
surface_type,
) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestAbovePreliminarySurface { skip } => {
if !test_above_preliminary_surface(ctx, sampler) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestHole { skip } => {
if !test_hole(ctx) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestSteep { skip } => {
if !test_steep(ctx, chunk) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestTemperature { skip } => {
if !test_temperature(ctx) {
pc += *skip as usize;
}
}
SurfaceInstruction::TestNot { inner, skip } => {
let passed = eval_single_instruction(inner, chunk, ctx, sampler);
if passed {
pc += *skip as usize;
}
}
}
pc += 1;
}
None
}
#[inline]
fn eval_single_instruction(
instr: &SurfaceInstruction,
chunk: &mut ProtoChunk,
ctx: &mut MaterialRuleContext,
sampler: &mut SurfaceHeightEstimateSampler,
) -> bool { ) -> bool {
let x = ctx.block_pos_x; match condition {
let z = ctx.block_pos_z; MaterialCondition::Biome(biome) => BiomeMaterialCondition::test(biome.biome_is, context),
let deriver = ctx.random_deriver; MaterialCondition::NoiseThreshold(noise_threshold) => {
match instr { test_noise_threshold(noise_threshold, context)
SurfaceInstruction::TestBiome { biome_is, .. } => {
biome_is.iter().any(|b| std::ptr::eq(*b, ctx.biome))
} }
SurfaceInstruction::TestNoiseAbove { noise, min, .. } => { MaterialCondition::VerticalGradient(vertical_gradient) => {
ctx.noise_cache test_vertical_gradient(vertical_gradient, context)
.get(noise, || sample_noise(noise, deriver, x, z))
>= *min
} }
SurfaceInstruction::TestNoiseRange { MaterialCondition::YAbove(above_y) => test_above_y_material(above_y, context),
noise, min, max, .. MaterialCondition::Water(water) => test_water_material(water, context),
} => { MaterialCondition::Temperature => {
let v = ctx let temperature = context.biome.weather.compute_temperature(
.noise_cache context.block_pos_x as f64,
.get(noise, || sample_noise(noise, deriver, x, z));
v >= *min && v <= *max
}
SurfaceInstruction::TestHole { .. } => test_hole(ctx),
SurfaceInstruction::TestSteep { .. } => test_steep(ctx, chunk),
SurfaceInstruction::TestTemperature { .. } => test_temperature(ctx),
SurfaceInstruction::TestYAbove {
anchor,
surface_depth_multiplier,
add_stone_depth,
..
} => test_y_above(ctx, anchor, *surface_depth_multiplier, *add_stone_depth),
SurfaceInstruction::TestWater {
offset,
surface_depth_multiplier,
add_stone_depth,
..
} => test_water(ctx, *offset, *surface_depth_multiplier, *add_stone_depth),
SurfaceInstruction::TestAbovePreliminarySurface { .. } => {
test_above_preliminary_surface(ctx, sampler)
}
_ => false,
}
}
#[inline]
pub fn get_badlands_block(context: &MaterialRuleContext) -> &'static BlockState {
context.terrain_builder.get_terracotta_block(
context.block_pos_x,
context.block_pos_y, context.block_pos_y,
context.block_pos_z, context.block_pos_z as f64,
) context.sea_level,
);
temperature < 0.15f32
}
MaterialCondition::Steep => {
let local_x = context.block_pos_x & 15;
let local_z = context.block_pos_z & 15;
let local_z_sub = 0.max(local_z - 1);
let local_z_add = 15.min(local_z + 1);
let sub_height = chunk.top_block_height_exclusive(local_x, local_z_sub);
let add_height = chunk.top_block_height_exclusive(local_x, local_z_add);
if add_height >= sub_height + 4 {
true
} else {
let local_x_sub = 0.max(local_x - 1);
let local_x_add = 15.min(local_x + 1);
let sub_height = chunk.top_block_height_exclusive(local_x_sub, local_z);
let add_height = chunk.top_block_height_exclusive(local_x_add, local_z);
sub_height >= add_height + 4
}
}
MaterialCondition::Not(not) => {
test_not_material(not, chunk, context, surface_height_estimate_sampler)
}
MaterialCondition::Hole(_hole) => HoleMaterialCondition::test(context),
MaterialCondition::AbovePreliminarySurface(_above) => {
SurfaceMaterialCondition::test(context, surface_height_estimate_sampler)
}
MaterialCondition::StoneDepth(stone_depth) => test_stone_depth(stone_depth, context),
}
} }
#[inline] pub struct HoleMaterialCondition;
pub const fn test_hole(context: &MaterialRuleContext) -> bool {
impl HoleMaterialCondition {
pub const fn test(context: &MaterialRuleContext) -> bool {
context.run_depth <= 0 context.run_depth <= 0
} }
}
pub const fn test_y_above( pub const fn test_above_y_material(
condition: &AboveYMaterialCondition,
context: &MaterialRuleContext, context: &MaterialRuleContext,
anchor: &YOffset,
surface_depth_multiplier: i32,
add_stone_depth: bool,
) -> bool { ) -> bool {
context.block_pos_y context.block_pos_y
+ if add_stone_depth { + if condition.add_stone_depth {
context.stone_depth_above context.stone_depth_above
} else { } else {
0 0
} }
>= anchor.get_y(context.min_y as i16, context.height) >= condition.anchor.get_y(context.min_y as i16, context.height)
+ context.run_depth * surface_depth_multiplier + context.run_depth * condition.surface_depth_multiplier
} }
#[inline] pub fn test_not_material(
pub fn test_above_preliminary_surface( condition: &NotMaterialCondition,
chunk: &mut ProtoChunk,
context: &mut MaterialRuleContext,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> bool {
!test_condition(
condition.invert,
chunk,
context,
surface_height_estimate_sampler,
)
}
pub struct SurfaceMaterialCondition;
impl SurfaceMaterialCondition {
pub fn test(
context: &mut MaterialRuleContext, context: &mut MaterialRuleContext,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler, surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> bool { ) -> bool {
context.block_pos_y >= estimate_surface_height(context, surface_height_estimate_sampler) context.block_pos_y >= estimate_surface_height(context, surface_height_estimate_sampler)
} }
}
pub fn estimate_surface_height( pub fn estimate_surface_height(
context: &mut MaterialRuleContext, context: &mut MaterialRuleContext,
@ -405,33 +283,41 @@ pub fn estimate_surface_height(
context.surface_min_y context.surface_min_y
} }
pub fn sample_noise( pub struct BiomeMaterialCondition;
parameters: &DoublePerlinNoiseParameters,
random_deriver: &XoroshiroSplitter, impl BiomeMaterialCondition {
x: i32, pub fn test(biome_is: &[&'static Biome], context: &MaterialRuleContext) -> bool {
z: i32, biome_is.contains(&context.biome)
) -> f64 { }
let sampler = DoublePerlinNoiseBuilder::get_noise_sampler_for_id(random_deriver, parameters); }
sampler.sample(x as f64, 0.0, z as f64)
pub fn test_noise_threshold(
condition: &NoiseThresholdMaterialCondition,
context: &mut MaterialRuleContext,
) -> bool {
// TODO: we want to cache these
let sampler = DoublePerlinNoiseBuilder::get_noise_sampler_for_id(
context.random_deriver,
&condition.noise,
);
let value = sampler.sample(context.block_pos_x as f64, 0.0, context.block_pos_z as f64);
value >= condition.min_threshold && value <= condition.max_threshold
} }
pub fn test_stone_depth( pub fn test_stone_depth(
condition: &StoneDepthMaterialCondition,
context: &mut MaterialRuleContext, context: &mut MaterialRuleContext,
offset: i32,
add_surface_depth: bool,
secondary_depth_range: i32,
surface_type: &VerticalSurfaceType,
) -> bool { ) -> bool {
let stone_depth = match surface_type { let stone_depth = match &condition.surface_type {
VerticalSurfaceType::Ceiling => context.stone_depth_below, VerticalSurfaceType::Ceiling => context.stone_depth_below,
VerticalSurfaceType::Floor => context.stone_depth_above, VerticalSurfaceType::Floor => context.stone_depth_above,
}; };
let depth = if add_surface_depth { let depth = if condition.add_surface_depth {
context.run_depth context.run_depth
} else { } else {
0 0
}; };
let depth_range = if secondary_depth_range == 0 { let depth_range = if condition.secondary_depth_range == 0 {
0 0
} else { } else {
pumpkin_util::math::map( pumpkin_util::math::map(
@ -439,37 +325,40 @@ pub fn test_stone_depth(
-1.0, -1.0,
1.0, 1.0,
0.0, 0.0,
secondary_depth_range as f64, condition.secondary_depth_range as f64,
) as i32 ) as i32
}; };
stone_depth <= 1 + offset + depth + depth_range stone_depth <= 1 + condition.offset + depth + depth_range
} }
pub const fn test_water( pub const fn test_water_material(
condition: &WaterMaterialCondition,
context: &MaterialRuleContext, context: &MaterialRuleContext,
offset: i32,
surface_depth_multiplier: i32,
add_stone_depth: bool,
) -> bool { ) -> bool {
context.fluid_height == i32::MIN context.fluid_height == i32::MIN
|| context.block_pos_y || context.block_pos_y
+ (if add_stone_depth { + (if condition.add_stone_depth {
context.stone_depth_above context.stone_depth_above
} else { } else {
0 0
}) })
>= context.fluid_height + offset + context.run_depth * surface_depth_multiplier >= context.fluid_height
+ condition.offset
+ context.run_depth * condition.surface_depth_multiplier
} }
// random_deriver: ThreadLocal<RefCell<LruCache<usize, RandomDeriver>>>,
pub fn test_vertical_gradient( pub fn test_vertical_gradient(
condition: &VerticalGradientMaterialCondition,
context: &MaterialRuleContext, context: &MaterialRuleContext,
random_lo: u64,
random_hi: u64,
true_at_and_below: &YOffset,
false_at_and_above: &YOffset,
) -> bool { ) -> bool {
let true_at = true_at_and_below.get_y(context.min_y as i16, context.height); let true_at = condition
let false_at = false_at_and_above.get_y(context.min_y as i16, context.height); .true_at_and_below
.get_y(context.min_y as i16, context.height);
let false_at = condition
.false_at_and_above
.get_y(context.min_y as i16, context.height);
let block_y = context.block_pos_y; let block_y = context.block_pos_y;
if block_y <= true_at { if block_y <= true_at {
@ -480,42 +369,9 @@ pub fn test_vertical_gradient(
} }
let splitter = context let splitter = context
.random_deriver .random_deriver
.from_lo_and_hi(random_lo, random_hi) .from_lo_and_hi(condition.random_lo, condition.random_hi)
.next_splitter(); .next_splitter();
let mapped = pumpkin_util::math::map(block_y as f32, true_at as f32, false_at as f32, 1.0, 0.0); let mapped = pumpkin_util::math::map(block_y as f32, true_at as f32, false_at as f32, 1.0, 0.0);
let mut random = splitter.split_pos(context.block_pos_x, block_y, context.block_pos_z); let mut random = splitter.split_pos(context.block_pos_x, block_y, context.block_pos_z);
random.next_f32() < mapped random.next_f32() < mapped
} }
pub fn test_steep(context: &MaterialRuleContext, chunk: &ProtoChunk) -> bool {
let local_x = context.block_pos_x & 15;
let local_z = context.block_pos_z & 15;
let local_z_sub = 0.max(local_z - 1);
let local_z_add = 15.min(local_z + 1);
let sub_height = chunk.top_block_height_exclusive(local_x, local_z_sub);
let add_height = chunk.top_block_height_exclusive(local_x, local_z_add);
if add_height >= sub_height + 4 {
true
} else {
let local_x_sub = 0.max(local_x - 1);
let local_x_add = 15.min(local_x + 1);
let sub_height = chunk.top_block_height_exclusive(local_x_sub, local_z);
let add_height = chunk.top_block_height_exclusive(local_x_add, local_z);
sub_height >= add_height + 4
}
}
pub fn test_temperature(context: &MaterialRuleContext) -> bool {
let temperature = context.biome.weather.compute_temperature(
context.block_pos_x as f64,
context.block_pos_y,
context.block_pos_z as f64,
context.sea_level,
);
temperature < 0.15f32
}

View File

@ -0,0 +1,89 @@
use pumpkin_data::{
BlockState,
chunk_gen_settings::{ConditionMaterialRule, MaterialRule, SequenceMaterialRule},
};
use super::MaterialRuleContext;
use crate::{
ProtoChunk,
generation::{
noise::router::surface_height_sampler::SurfaceHeightEstimateSampler,
surface::test_condition,
},
};
pub fn try_apply_material_rule(
rule: &MaterialRule,
chunk: &mut ProtoChunk,
context: &mut MaterialRuleContext,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
match rule {
MaterialRule::Badlands(_badlands) => Some(BadLandsMaterialRule::try_apply(context)),
MaterialRule::Block(block) => Some(BlockMaterialRule::try_apply(block.result_state)),
MaterialRule::Sequence(sequence) => {
try_apply_sequence(sequence, chunk, context, surface_height_estimate_sampler)
}
MaterialRule::Condition(condition) => {
try_apply_condition(condition, chunk, context, surface_height_estimate_sampler)
}
}
}
pub struct BadLandsMaterialRule;
impl BadLandsMaterialRule {
pub fn try_apply(context: &mut MaterialRuleContext) -> &'static BlockState {
context.terrain_builder.get_terracotta_block(
context.block_pos_x,
context.block_pos_y,
context.block_pos_z,
)
}
}
pub struct BlockMaterialRule;
impl BlockMaterialRule {
pub const fn try_apply(state: &'static BlockState) -> &'static BlockState {
state
}
}
pub fn try_apply_sequence(
rule: &SequenceMaterialRule,
chunk: &mut ProtoChunk,
context: &mut MaterialRuleContext,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
for seq in rule.sequence {
if let Some(state) =
try_apply_material_rule(seq, chunk, context, surface_height_estimate_sampler)
{
return Some(state);
}
}
None
}
pub fn try_apply_condition(
rule: &ConditionMaterialRule,
chunk: &mut ProtoChunk,
context: &mut MaterialRuleContext,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) -> Option<&'static BlockState> {
if test_condition(
&rule.if_true,
chunk,
context,
surface_height_estimate_sampler,
) {
return try_apply_material_rule(
rule.then_run,
chunk,
context,
surface_height_estimate_sampler,
);
}
None
}