Skip to main content

steel_core/worldgen/feature/
providers.rs

1use super::prelude::*;
2use super::runner::FeatureDecorationRunner;
3use smallvec::SmallVec;
4use steel_math::map_clamped;
5
6impl FeatureDecorationRunner {
7    pub(super) fn sample_block_state_provider_optional(
8        region: &WorldGenRegion<'_>,
9        registry: &Registry,
10        random: &mut WorldgenRandom,
11        provider: &BlockStateProvider,
12        pos: BlockPos,
13    ) -> Option<BlockStateId> {
14        match provider {
15            BlockStateProvider::RuleBased { fallback, rules } => {
16                for rule in rules {
17                    if Self::test_block_predicate(region, registry, &rule.if_true, pos) {
18                        return Some(Self::sample_block_state_provider(
19                            region, registry, random, &rule.then, pos,
20                        ));
21                    }
22                }
23
24                fallback.as_ref().map(|fallback| {
25                    Self::sample_block_state_provider(region, registry, random, fallback, pos)
26                })
27            }
28            _ => Some(Self::sample_block_state_provider(
29                region, registry, random, provider, pos,
30            )),
31        }
32    }
33
34    pub(super) fn sample_block_state_provider(
35        region: &WorldGenRegion<'_>,
36        registry: &Registry,
37        random: &mut WorldgenRandom,
38        provider: &BlockStateProvider,
39        pos: BlockPos,
40    ) -> BlockStateId {
41        match provider {
42            BlockStateProvider::Simple { state } => Self::block_state_from_data(registry, state),
43            BlockStateProvider::Weighted { entries } => {
44                assert!(
45                    !entries.is_empty(),
46                    "weighted block-state provider must not be empty"
47                );
48                let total_weight = entries.iter().fold(0, |total, entry| {
49                    assert!(
50                        entry.weight > 0,
51                        "weighted block-state provider entry weight must be positive, got {}",
52                        entry.weight
53                    );
54                    total + entry.weight
55                });
56                let mut target = random.next_i32_bounded(total_weight);
57                for entry in entries {
58                    if target < entry.weight {
59                        return Self::block_state_from_data(registry, &entry.data);
60                    }
61                    target -= entry.weight;
62                }
63                panic!("weighted block-state provider failed to select an entry");
64            }
65            BlockStateProvider::RotatedBlock { state } => {
66                let state = Self::block_state_from_data(registry, state);
67                state.set_value(&BlockStateProperties::AXIS, Self::random_axis(random))
68            }
69            BlockStateProvider::RandomizedInt {
70                property,
71                source,
72                values,
73            } => {
74                let state =
75                    Self::sample_block_state_provider(region, registry, random, source, pos);
76                let value = values.sample(random);
77                Self::set_int_property_by_name(registry, state, property, value)
78            }
79            BlockStateProvider::RuleBased { .. } => {
80                if let Some(state) = Self::sample_block_state_provider_optional(
81                    region, registry, random, provider, pos,
82                ) {
83                    state
84                } else {
85                    region.block_state(pos)
86                }
87            }
88            BlockStateProvider::Noise(provider) => {
89                Self::sample_noise_provider(registry, provider, pos)
90            }
91            BlockStateProvider::NoiseThreshold(provider) => {
92                Self::sample_noise_threshold_provider(registry, random, provider, pos)
93            }
94            BlockStateProvider::DualNoise(provider) => {
95                Self::sample_dual_noise_provider(registry, provider, pos)
96            }
97        }
98    }
99
100    pub(super) fn random_axis(random: &mut WorldgenRandom) -> Axis {
101        match random.next_i32_bounded(3) {
102            0 => Axis::X,
103            1 => Axis::Y,
104            _ => Axis::Z,
105        }
106    }
107
108    pub(super) fn set_int_property_by_name(
109        registry: &Registry,
110        state: BlockStateId,
111        property: &str,
112        value: i32,
113    ) -> BlockStateId {
114        let Some(block) = registry.blocks.by_state_id(state) else {
115            panic!("block-state provider received invalid block state id {state:?}");
116        };
117        let value_string = value.to_string();
118        let current_properties = registry.blocks.get_properties(state);
119        let mut found = false;
120        let properties = current_properties
121            .iter()
122            .map(|(name, existing)| {
123                if *name == property {
124                    found = true;
125                    (*name, value_string.as_str())
126                } else {
127                    (*name, *existing)
128                }
129            })
130            .collect::<Vec<_>>();
131
132        if !found {
133            return state;
134        }
135
136        let Some(new_state) = registry
137            .blocks
138            .state_id_from_block_properties(block, &properties)
139        else {
140            panic!(
141                "randomized int provider produced invalid value {value} for property {property} on {}",
142                block.key
143            );
144        };
145        new_state
146    }
147
148    pub(super) fn sample_noise_provider(
149        registry: &Registry,
150        provider: &NoiseProvider,
151        pos: BlockPos,
152    ) -> BlockStateId {
153        let noise = Self::normal_noise(&provider.noise, provider.seed);
154        let noise_value = Self::noise_value(&noise, pos, provider.scale);
155        Self::noise_state_by_value(registry, &provider.states, noise_value)
156    }
157
158    pub(super) fn sample_noise_threshold_provider(
159        registry: &Registry,
160        random: &mut WorldgenRandom,
161        provider: &NoiseThresholdProvider,
162        pos: BlockPos,
163    ) -> BlockStateId {
164        let noise = Self::normal_noise(&provider.noise, provider.seed);
165        let noise_value = Self::noise_value(&noise, pos, provider.scale);
166        if noise_value < f64::from(provider.threshold) {
167            Self::random_block_state_from_data_list(registry, random, &provider.low_states)
168        } else if random.next_f32() < provider.high_chance {
169            Self::random_block_state_from_data_list(registry, random, &provider.high_states)
170        } else {
171            Self::block_state_from_data(registry, &provider.default_state)
172        }
173    }
174
175    pub(super) fn sample_dual_noise_provider(
176        registry: &Registry,
177        provider: &DualNoiseProvider,
178        pos: BlockPos,
179    ) -> BlockStateId {
180        let slow_noise = Self::normal_noise(&provider.slow_noise, provider.seed);
181        let variety_noise = Self::noise_value(&slow_noise, pos, provider.slow_scale);
182        let local_variety = map_clamped(
183            variety_noise,
184            -1.0,
185            1.0,
186            f64::from(provider.variety[0]),
187            f64::from(provider.variety[1] + 1),
188        ) as i32;
189        assert!(
190            local_variety > 0,
191            "dual-noise provider local variety must be positive, got {local_variety}"
192        );
193
194        let Ok(capacity) = usize::try_from(local_variety) else {
195            panic!("dual-noise provider local variety {local_variety} exceeds usize range");
196        };
197        let mut possible_states = SmallVec::<[BlockStateId; 8]>::with_capacity(capacity);
198        for i in 0..local_variety {
199            let offset_pos = pos.offset(i * 54_545, 0, i * 34_234);
200            let slow_value = Self::noise_value(&slow_noise, offset_pos, provider.slow_scale);
201            possible_states.push(Self::noise_state_by_value(
202                registry,
203                &provider.states,
204                slow_value,
205            ));
206        }
207
208        let noise = Self::normal_noise(&provider.noise, provider.seed);
209        let noise_value = Self::noise_value(&noise, pos, provider.scale);
210        Self::noise_state_by_resolved_value(&possible_states, noise_value)
211    }
212
213    pub(super) fn normal_noise(parameters: &FeatureNoiseParameters, seed: i64) -> NormalNoise {
214        let mut random = RandomSource::Legacy(LegacyRandom::from_seed(seed as u64));
215        NormalNoise::create_from_random(
216            &mut random,
217            parameters.first_octave,
218            &parameters.amplitudes,
219        )
220    }
221
222    pub(super) fn noise_value(noise: &NormalNoise, pos: BlockPos, scale: f32) -> f64 {
223        let scale = f64::from(scale);
224        noise.get_value(
225            f64::from(pos.x()) * scale,
226            f64::from(pos.y()) * scale,
227            f64::from(pos.z()) * scale,
228        )
229    }
230
231    pub(super) fn noise_state_by_value(
232        registry: &Registry,
233        states: &[BlockStateData],
234        noise_value: f64,
235    ) -> BlockStateId {
236        assert!(
237            !states.is_empty(),
238            "noise provider state list must not be empty"
239        );
240        let index = Self::noise_state_index(states.len(), noise_value);
241        Self::block_state_from_data(registry, &states[index])
242    }
243
244    pub(super) fn noise_state_by_resolved_value(
245        states: &[BlockStateId],
246        noise_value: f64,
247    ) -> BlockStateId {
248        assert!(
249            !states.is_empty(),
250            "noise provider state list must not be empty"
251        );
252        states[Self::noise_state_index(states.len(), noise_value)]
253    }
254
255    pub(super) fn noise_state_index(state_count: usize, noise_value: f64) -> usize {
256        let placement_value = f64::midpoint(1.0, noise_value).clamp(0.0, 0.9999);
257        (placement_value * state_count as f64) as usize
258    }
259
260    pub(super) fn random_block_state_from_data_list(
261        registry: &Registry,
262        random: &mut WorldgenRandom,
263        states: &[BlockStateData],
264    ) -> BlockStateId {
265        assert!(
266            !states.is_empty(),
267            "random block-state provider state list must not be empty"
268        );
269        let Ok(state_count) = i32::try_from(states.len()) else {
270            panic!(
271                "random block-state provider state count {} exceeds i32 range",
272                states.len()
273            );
274        };
275        let index = random.next_i32_bounded(state_count) as usize;
276        Self::block_state_from_data(registry, &states[index])
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::FeatureDecorationRunner;
283
284    #[test]
285    fn noise_state_index_uses_vanilla_placement_value_formula() {
286        for (state_count, noise_value) in
287            [(2, -1.5), (4, -0.5), (8, 0.0), (16, 0.75), (32, 1.5)] as [(usize, f64); 5]
288        {
289            let placement_value = f64::midpoint(1.0, noise_value).clamp(0.0, 0.9999);
290            let expected = (placement_value * state_count as f64) as usize;
291
292            assert_eq!(
293                FeatureDecorationRunner::noise_state_index(state_count, noise_value),
294                expected
295            );
296        }
297    }
298}