diff --git a/crates/horizon-world/src/worldgen/material_condition.rs b/crates/horizon-world/src/worldgen/material_condition.rs index 6433b09a4..d0b6c4290 100644 --- a/crates/horizon-world/src/worldgen/material_condition.rs +++ b/crates/horizon-world/src/worldgen/material_condition.rs @@ -23,6 +23,7 @@ use horizon_core::{ pub use library::MaterialRuleLibrary; pub use noise::MaterialNoiseSources; use rule::CompiledRule; +pub use rule::SurfaceVariants; pub use rule::{MaterialRule, MaterialRuleError}; use std::{ collections::{BTreeMap, BTreeSet}, @@ -299,7 +300,81 @@ enum Kind { }, Not(Box>), } +/// What a specialised rule may assume about every position it is applied to +/// (`CompiledRule::specialize`): its y lies in `low..=high`, and whether it is +/// at or above the column's preliminary surface level, when known. +#[derive(Clone, Copy, Debug)] +pub struct Facts { + pub low: i32, + pub high: i32, + pub above_surface: Option, +} +impl Clone for Kind +where + R::Positional: Clone, +{ + fn clone(&self) -> Self { + match self { + Self::Simple(condition) => Self::Simple(condition.clone()), + Self::Biome(names) => Self::Biome(names.clone()), + Self::Constant(value) => Self::Constant(*value), + Self::Noise { sampler, min, max } => Self::Noise { + sampler: *sampler, + min: *min, + max: *max, + }, + Self::Gradient { low, high, random } => Self::Gradient { + low: *low, + high: *high, + random: random.clone(), + }, + Self::Not(child) => Self::Not(child.clone()), + } + } +} +impl Clone for Compiled +where + R::Positional: Clone, +{ + fn clone(&self) -> Self { + Self { + kind: self.kind.clone(), + revision: self.revision, + result: self.result, + } + } +} impl Compiled { + /// The value `facts` decide for this condition at every position they + /// cover. Conditions read the position and the world, never change them, + /// so one decided in advance gives what testing it would. + fn decided(&self, facts: Facts) -> Option { + match &self.kind { + Kind::Constant(value) => Some(*value), + Kind::Simple(MaterialCondition::AbovePreliminarySurface) => facts.above_surface, + Kind::Gradient { low, high, .. } => { + if facts.high <= *low { + Some(true) + } else if facts.low >= *high { + Some(false) + } else { + None + } + } + Kind::Not(child) => child.decided(facts).map(|value| !value), + _ => None, + } + } + /// The vertical gradients' bounds, and whether the preliminary surface + /// is asked for. + fn inputs(&self, gradients: &mut Vec<(i32, i32)>, surface: &mut bool) { + match &self.kind { + Kind::Gradient { low, high, .. } => gradients.push((*low, *high)), + Kind::Simple(MaterialCondition::AbovePreliminarySurface) => *surface = true, + Kind::Not(child) => child.inputs(gradients, surface), + _ => {} + } + } fn constant_result(&self) -> Option { match &self.kind { Kind::Constant(value) => Some(*value), diff --git a/crates/horizon-world/src/worldgen/material_condition/rule.rs b/crates/horizon-world/src/worldgen/material_condition/rule.rs index cf4fc8547..f3c2ae60d 100644 --- a/crates/horizon-world/src/worldgen/material_condition/rule.rs +++ b/crates/horizon-world/src/worldgen/material_condition/rule.rs @@ -1,5 +1,7 @@ //! Compiled material rule composition, sharing the condition worker's context. -use super::{Compiled, Context, MaterialCondition, MaterialConditions, MaterialSurfaceQueries}; +use super::{ + Compiled, Context, Facts, MaterialCondition, MaterialConditions, MaterialSurfaceQueries, +}; use crate::{registry::BlockStateId, worldgen::DecodeError}; use horizon_core::{ density::DensitySampler, @@ -209,6 +211,161 @@ impl CompiledRule { } } } +impl CompiledRule +where + R::Positional: Clone, +{ + /// This rule where `facts` hold: conditions they decide are folded away, + /// with what can no longer produce a block, and a sequence stops at its + /// first child that always produces one. For every position the facts + /// cover it gives the block the whole rule gives. + fn specialize(&self, facts: Facts) -> Self { + match self { + Self::Empty => Self::Empty, + Self::Block(block) => Self::Block(*block), + Self::Bandlands => Self::Bandlands, + Self::OreVein { + blocks, + raw_ore_chance, + densities, + random, + } => Self::OreVein { + blocks: *blocks, + raw_ore_chance: *raw_ore_chance, + densities: *densities, + random: random.clone(), + }, + Self::Condition { condition, rule } => match condition.decided(facts) { + Some(false) => Self::Empty, + Some(true) => rule.specialize(facts), + None => match rule.specialize(facts) { + Self::Empty => Self::Empty, + rule => Self::Condition { + condition: condition.clone(), + rule: Box::new(rule), + }, + }, + }, + Self::Sequence(children) => { + let mut result = Vec::new(); + for child in children { + let child = child.specialize(facts); + if matches!(child, Self::Empty) { + continue; + } + let last = child.always_produces(); + result.push(child); + if last { + break; + } + } + match result.len() { + 0 => Self::Empty, + 1 => result.pop().unwrap(), + _ => Self::Sequence(result), + } + } + } + } + /// Whether applying this rule never gives `None` (only an error). + fn always_produces(&self) -> bool { + match self { + Self::Block(_) | Self::Bandlands => true, + Self::Sequence(children) => children.iter().any(Self::always_produces), + Self::Empty | Self::OreVein { .. } | Self::Condition { .. } => false, + } + } + fn inputs(&self, gradients: &mut Vec<(i32, i32)>, surface: &mut bool) { + match self { + Self::Condition { condition, rule } => { + condition.inputs(gradients, surface); + rule.inputs(gradients, surface); + } + Self::Sequence(children) => { + for child in children { + child.inputs(gradients, surface); + } + } + _ => {} + } + } +} +/// A rule specialised (`CompiledRule::specialize`) for each band of y between +/// its vertical gradients' bounds, and below and at or above the preliminary +/// surface when the rule asks for it: most blocks of a column, deep stone, +/// then evaluate only the few rules that can apply there. +pub struct SurfaceVariants { + /// The first y of each band after the first, ascending. + starts: Vec, + /// The rule index per band (two per band, below then above the surface, + /// when `surface`). + rules: Vec, + pub surface: bool, +} +impl SurfaceVariants { + /// The rule for a block at `y`, `above` the preliminary surface (ignored + /// unless `surface`). + #[inline] + pub fn rule(&self, y: i32, above: bool) -> usize { + let band = self.starts.partition_point(|&start| start <= y); + if self.surface { + self.rules[band * 2 + usize::from(above)] + } else { + self.rules[band] + } + } +} +impl MaterialConditions +where + R::Positional: Clone, +{ + /// Add the specialised variants of rule `index` (`SurfaceVariants`). + pub fn surface_variants(&mut self, index: usize) -> Result { + let rule = self + .rules + .get(index) + .ok_or(MaterialRuleError::UnknownRule)?; + let (mut gradients, mut surface) = (Vec::new(), false); + rule.inputs(&mut gradients, &mut surface); + // A gradient is true at and below `low`, random between, false at + // and above `high`: it is decided within each band these bound. + let mut starts: Vec = gradients + .iter() + .flat_map(|&(low, high)| [low.saturating_add(1), high]) + .collect(); + starts.sort_unstable(); + starts.dedup(); + let mut variants = Vec::new(); + for band in 0..=starts.len() { + let low = if band == 0 { + i32::MIN + } else { + starts[band - 1] + }; + let high = starts.get(band).map_or(i32::MAX, |&next| next - 1); + let sides: &[Option] = if surface { + &[Some(false), Some(true)] + } else { + &[None] + }; + for &above_surface in sides { + let facts = Facts { + low, + high, + above_surface, + }; + variants.push(self.rules[index].specialize(facts)); + } + } + let first = self.rules.len(); + self.rules.extend(variants); + Ok(SurfaceVariants { + starts, + rules: (first..self.rules.len()).collect(), + surface, + }) + } +} impl MaterialConditions { /// Compile all children in order, even after an unconditional block rule. /// Failed compilation never adds a partial rule to this worker. diff --git a/crates/horizon-world/src/worldgen/material_stage.rs b/crates/horizon-world/src/worldgen/material_stage.rs index 0763c4e63..fd51d3716 100644 --- a/crates/horizon-world/src/worldgen/material_stage.rs +++ b/crates/horizon-world/src/worldgen/material_stage.rs @@ -196,7 +196,10 @@ pub fn build_surface, extensions: &MaterialExtensions, -) -> Result<(), SurfaceError> { +) -> Result<(), SurfaceError> +where + R::Positional: Clone, +{ let min = chunk.min_y(); let max = min + chunk.height() - 1; if inputs.write_min_y < min @@ -240,9 +243,13 @@ fn build_columns, write_min_y: i32, write_max_y: i32, -) -> Result<(), SurfaceError> { +) -> Result<(), SurfaceError> +where + R::Positional: Clone, +{ let min = chunk.min_y(); let rule = worker.add_rule(rule)?; + let variants = worker.surface_variants(rule)?; let cx = chunk.position().x.wrapping_shl(4); let cz = chunk.position().z.wrapping_shl(4); for lx in 0..16 { @@ -321,6 +328,8 @@ fn build_columns= write_min_y && y <= write_max_y { + let above = variants.surface && y >= worker.minimum_surface_level()?; + let rule = variants.rule(y, above); if let Some(state) = worker.try_apply(rule, BlockPos::new(x, y, z))? { column.set_block(y, state)?; }