1use std::f32::consts::TAU;
11
12use steel_utils::BlockPos;
13use steel_utils::climate::{Parameter, ParameterPoint, TargetPoint, quantize_coord};
14use steel_utils::random::{Random, xoroshiro::Xoroshiro};
15use steel_worldgen::density_functions::overworld::{self, OverworldColumnCache, OverworldNoises};
16use steel_worldgen::noise_parameters::get_noise_parameters;
17
18pub struct OverworldClimateSampler {
24 noises: Box<OverworldNoises>,
27}
28
29impl OverworldClimateSampler {
30 #[must_use]
32 pub fn new(seed: u64) -> Self {
33 let mut rng = Xoroshiro::from_seed(seed);
34 let splitter = rng.next_positional();
35 let noise_params = get_noise_parameters();
36 let noises = OverworldNoises::create(seed, &splitter, &noise_params);
37
38 Self {
39 noises: Box::new(noises),
40 }
41 }
42
43 #[must_use]
49 pub fn sample(
50 &self,
51 quart_x: i32,
52 quart_y: i32,
53 quart_z: i32,
54 cache: &mut OverworldColumnCache,
55 ) -> TargetPoint {
56 let block_x = quart_x << 2;
57 let block_y = quart_y << 2;
58 let block_z = quart_z << 2;
59
60 cache.ensure(block_x, block_z, &self.noises);
62
63 let block_x = f64::from(block_x);
64 let block_y = f64::from(block_y);
65 let block_z = f64::from(block_z);
66
67 let temp =
70 overworld::router_temperature(&self.noises, cache, block_x, block_y, block_z) as f32;
71 let humidity =
72 overworld::router_vegetation(&self.noises, cache, block_x, block_y, block_z) as f32;
73 let cont = overworld::router_continentalness(&self.noises, cache, block_x, block_y, block_z)
74 as f32;
75 let erosion =
76 overworld::router_erosion(&self.noises, cache, block_x, block_y, block_z) as f32;
77 let depth = overworld::router_depth(&self.noises, cache, block_x, block_y, block_z) as f32;
78 let weirdness =
79 overworld::router_ridges(&self.noises, cache, block_x, block_y, block_z) as f32;
80
81 TargetPoint::new(
82 quantize_coord(f64::from(temp)),
83 quantize_coord(f64::from(humidity)),
84 quantize_coord(f64::from(cont)),
85 quantize_coord(f64::from(erosion)),
86 quantize_coord(f64::from(depth)),
87 quantize_coord(f64::from(weirdness)),
88 )
89 }
90
91 pub fn init_column_grid(
101 &self,
102 cache: &mut OverworldColumnCache,
103 chunk_block_x: i32,
104 chunk_block_z: i32,
105 ) {
106 cache.init_grid(chunk_block_x, chunk_block_z, &self.noises);
107 }
108
109 #[must_use]
114 pub fn find_spawn_position(&self) -> BlockPos {
115 let spawn_targets = overworld_spawn_targets();
116 let mut result = self.spawn_position_and_fitness(&spawn_targets, 0, 0);
117 result = self.radial_spawn_search(&spawn_targets, result, 2048.0, 512.0);
118 self.radial_spawn_search(&spawn_targets, result, 512.0, 32.0)
119 .pos
120 }
121
122 fn radial_spawn_search(
123 &self,
124 spawn_targets: &[ParameterPoint; 2],
125 mut result: SpawnSearchResult,
126 max_radius: f32,
127 radius_increment: f32,
128 ) -> SpawnSearchResult {
129 let mut angle = 0.0_f32;
130 let mut radius = radius_increment;
131 let origin = result.pos;
132
133 while radius <= max_radius {
134 let x = origin.x() + (angle.sin() * radius) as i32;
135 let z = origin.z() + (angle.cos() * radius) as i32;
136 let candidate = self.spawn_position_and_fitness(spawn_targets, x, z);
137 if candidate.fitness < result.fitness {
138 result = candidate;
139 }
140
141 angle += radius_increment / radius;
142 if angle > TAU {
143 angle = 0.0;
144 radius += radius_increment;
145 }
146 }
147
148 result
149 }
150
151 fn spawn_position_and_fitness(
152 &self,
153 spawn_targets: &[ParameterPoint; 2],
154 block_x: i32,
155 block_z: i32,
156 ) -> SpawnSearchResult {
157 let mut cache = OverworldColumnCache::new();
158 let target = self.sample(block_x >> 2, 0, block_z >> 2, &mut cache);
159 let zero_depth_target = TargetPoint::new(
160 target.temperature,
161 target.humidity,
162 target.continentalness,
163 target.erosion,
164 0,
165 target.weirdness,
166 );
167 let min_fitness = spawn_targets
168 .iter()
169 .map(|point| point.fitness(&zero_depth_target))
170 .min()
171 .unwrap_or(i64::MAX);
172 let distance_bias =
173 i64::from(block_x) * i64::from(block_x) + i64::from(block_z) * i64::from(block_z);
174
175 SpawnSearchResult {
176 pos: BlockPos::new(block_x, 0, block_z),
177 fitness: min_fitness * 2048_i64 * 2048_i64 + distance_bias,
178 }
179 }
180}
181
182#[derive(Clone, Copy)]
183struct SpawnSearchResult {
184 pos: BlockPos,
185 fitness: i64,
186}
187
188fn overworld_spawn_targets() -> [ParameterPoint; 2] {
189 let full_range = Parameter::span(-1.0, 1.0);
190 let inland_continentalness = Parameter::span(-0.11, 0.55);
191 let continentalness = Parameter::span_params(&inland_continentalness, &full_range);
192 let surface_depth = Parameter::point(0.0);
193
194 [
195 ParameterPoint::new(
196 full_range,
197 full_range,
198 continentalness,
199 full_range,
200 surface_depth,
201 Parameter::span(-1.0, -0.16),
202 0,
203 ),
204 ParameterPoint::new(
205 full_range,
206 full_range,
207 continentalness,
208 full_range,
209 surface_depth,
210 Parameter::span(0.16, 1.0),
211 0,
212 ),
213 ]
214}