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 ¶meters.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}