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 = overworld::router_temperature(&self.noises, cache, block_x, block_y, block_z);
68 let humidity = overworld::router_vegetation(&self.noises, cache, block_x, block_y, block_z);
69 let cont =
70 overworld::router_continentalness(&self.noises, cache, block_x, block_y, block_z);
71 let erosion = overworld::router_erosion(&self.noises, cache, block_x, block_y, block_z);
72 let depth = overworld::router_depth(&self.noises, cache, block_x, block_y, block_z);
73 let weirdness = overworld::router_ridges(&self.noises, cache, block_x, block_y, block_z);
74
75 TargetPoint::new(
76 quantize_coord(f64::from(temp)),
77 quantize_coord(f64::from(humidity)),
78 quantize_coord(f64::from(cont)),
79 quantize_coord(f64::from(erosion)),
80 quantize_coord(f64::from(depth)),
81 quantize_coord(f64::from(weirdness)),
82 )
83 }
84
85 pub fn init_column_grid(
95 &self,
96 cache: &mut OverworldColumnCache,
97 chunk_block_x: i32,
98 chunk_block_z: i32,
99 ) {
100 cache.init_grid(chunk_block_x, chunk_block_z, &self.noises);
101 }
102
103 #[must_use]
108 pub fn find_spawn_position(&self) -> BlockPos {
109 let spawn_targets = overworld_spawn_targets();
110 let mut result = self.spawn_position_and_fitness(&spawn_targets, 0, 0);
111 result = self.radial_spawn_search(&spawn_targets, result, 2048.0, 512.0);
112 self.radial_spawn_search(&spawn_targets, result, 512.0, 32.0)
113 .pos
114 }
115
116 fn radial_spawn_search(
117 &self,
118 spawn_targets: &[ParameterPoint; 2],
119 mut result: SpawnSearchResult,
120 max_radius: f32,
121 radius_increment: f32,
122 ) -> SpawnSearchResult {
123 let mut angle = 0.0_f32;
124 let mut radius = radius_increment;
125 let origin = result.pos;
126
127 while radius <= max_radius {
128 let x = origin.x() + (angle.sin() * radius) as i32;
129 let z = origin.z() + (angle.cos() * radius) as i32;
130 let candidate = self.spawn_position_and_fitness(spawn_targets, x, z);
131 if candidate.fitness < result.fitness {
132 result = candidate;
133 }
134
135 angle += radius_increment / radius;
136 if angle > TAU {
137 angle = 0.0;
138 radius += radius_increment;
139 }
140 }
141
142 result
143 }
144
145 fn spawn_position_and_fitness(
146 &self,
147 spawn_targets: &[ParameterPoint; 2],
148 block_x: i32,
149 block_z: i32,
150 ) -> SpawnSearchResult {
151 let mut cache = OverworldColumnCache::new();
152 let target = self.sample(block_x >> 2, 0, block_z >> 2, &mut cache);
153 let zero_depth_target = TargetPoint::new(
154 target.temperature,
155 target.humidity,
156 target.continentalness,
157 target.erosion,
158 0,
159 target.weirdness,
160 );
161 let min_fitness = spawn_targets
162 .iter()
163 .map(|point| point.fitness(&zero_depth_target))
164 .min()
165 .unwrap_or(i64::MAX);
166 let distance_bias =
167 i64::from(block_x) * i64::from(block_x) + i64::from(block_z) * i64::from(block_z);
168
169 SpawnSearchResult {
170 pos: BlockPos::new(block_x, 0, block_z),
171 fitness: min_fitness * 2048_i64 * 2048_i64 + distance_bias,
172 }
173 }
174}
175
176#[derive(Clone, Copy)]
177struct SpawnSearchResult {
178 pos: BlockPos,
179 fitness: i64,
180}
181
182fn overworld_spawn_targets() -> [ParameterPoint; 2] {
183 let full_range = Parameter::span(-1.0, 1.0);
184 let inland_continentalness = Parameter::span(-0.11, 0.55);
185 let continentalness = Parameter::span_params(&inland_continentalness, &full_range);
186 let surface_depth = Parameter::point(0.0);
187
188 [
189 ParameterPoint::new(
190 full_range,
191 full_range,
192 continentalness,
193 full_range,
194 surface_depth,
195 Parameter::span(-1.0, -0.16),
196 0,
197 ),
198 ParameterPoint::new(
199 full_range,
200 full_range,
201 continentalness,
202 full_range,
203 surface_depth,
204 Parameter::span(0.16, 1.0),
205 0,
206 ),
207 ]
208}