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