1use crate::{
2 CONFIG, IndexRef,
3 column::ColumnSample,
4 sim::{RiverKind, WorldSim},
5 site::SiteKind,
6};
7use common::{
8 terrain::{
9 CoordinateConversions, NEIGHBOR_DELTA, TerrainChunkSize,
10 map::{Connection, ConnectionKind, MapConfig, MapSample},
11 vec2_as_uniform_idx,
12 },
13 vol::RectVolSize,
14};
15use std::f64;
16use vek::*;
17
18pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2<i32>) -> f32 {
37 let MapConfig {
38 focus,
39 gain,
40
41 is_basement,
42 is_water,
43 ..
44 } = *config;
45
46 (sampler
47 .get_wpos(wpos)
48 .map(|s| {
49 if is_basement { s.basement } else { s.alt }.max(if is_water {
50 s.water_alt
51 } else {
52 -f32::INFINITY
53 })
54 })
55 .unwrap_or(CONFIG.sea_level)
56 - focus.z as f32)
57 / gain
58}
59
60pub fn sample_pos(
74 config: &MapConfig,
75 sampler: &WorldSim,
76 index: IndexRef,
77 samples: Option<&[Option<ColumnSample>]>,
78 pos: Vec2<i32>,
79) -> MapSample {
80 let map_size_lg = config.map_size_lg();
81 let MapConfig {
82 focus,
83 gain,
84
85 is_basement,
86 is_water,
87 is_ice,
88 is_shaded,
89 is_temperature,
90 is_humidity,
91 ..
93 } = *config;
94
95 let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64;
96
97 let (
98 chunk_idx,
99 alt,
100 basement,
101 water_alt,
102 humidity,
103 temperature,
104 downhill,
105 river_kind,
106 spline_derivative,
107 is_path,
108 is_bridge,
109 ) = sampler
110 .get(pos)
111 .map(|sample| {
112 (
113 Some(vec2_as_uniform_idx(map_size_lg, pos)),
114 sample.alt,
115 sample.basement,
116 sample.water_alt,
117 sample.humidity,
118 sample.temp,
119 sample.downhill,
120 sample.river.river_kind,
121 sample.river.spline_derivative,
122 sample.path.0.is_way(),
123 sample
124 .sites
125 .iter()
126 .any(|site| match &index.sites.get(*site).kind {
127 SiteKind::Bridge(bridge) => {
128 if let Some(plot) =
129 bridge.wpos_tile(TerrainChunkSize::center_wpos(pos)).plot
130 {
131 matches!(bridge.plot(plot).kind, crate::site2::PlotKind::Bridge(_))
132 } else {
133 false
134 }
135 },
136 _ => false,
137 }),
138 )
139 })
140 .unwrap_or((
141 None,
142 CONFIG.sea_level,
143 CONFIG.sea_level,
144 CONFIG.sea_level,
145 0.0,
146 0.0,
147 None,
148 None,
149 Vec2::zero(),
150 false,
151 false,
152 ));
153
154 let humidity = humidity.clamp(0.0, 1.0);
155 let temperature = temperature.clamp(-1.0, 1.0) * 0.5 + 0.5;
156 let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
157 let column_data = samples
158 .and_then(|samples| {
159 chunk_idx
160 .and_then(|chunk_idx| samples.get(chunk_idx))
161 .and_then(Option::as_ref)
162 })
163 .map(|sample| {
164 let alt = sample.alt;
166 let basement = sample.basement;
167 let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement);
168 let wposz = if is_basement { basement } else { alt };
169 let rgb = if is_basement && wposz < alt - grass_depth {
170 Lerp::lerp(
171 sample.sub_surface_color,
172 sample.stone_col.map(|e| e as f32 / 255.0),
173 (alt - grass_depth - wposz) * 0.15,
174 )
175 .map(|e| e as f64)
176 } else {
177 Lerp::lerp(
178 sample.sub_surface_color,
179 sample.surface_color,
180 ((wposz - (alt - grass_depth)) / grass_depth).sqrt(),
181 )
182 .map(|e| e as f64)
183 };
184
185 (rgb, alt, sample.ice_depth)
186 });
187
188 let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
189 let alt = if is_basement {
190 basement
191 } else {
192 column_data.map_or(alt, |(_, alt, _)| alt)
193 };
194
195 let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
196 let true_alt = (alt as f64 - focus.z) / gain as f64;
197 let water_depth = (true_water_alt - true_alt).clamp(0.0, 1.0);
198 let alt = true_alt.clamp(0.0, 1.0);
199
200 let water_color_factor = 2.0;
201 let g_water = 32.0 * water_color_factor;
202 let b_water = 64.0 * water_color_factor;
203 let default_rgb = Rgb::new(
204 if is_shaded || is_temperature {
205 1.0
206 } else {
207 0.0
208 },
209 if is_shaded { 1.0 } else { alt },
210 if is_shaded || is_humidity { 1.0 } else { 0.0 },
211 );
212 let column_rgb = column_data.map(|(rgb, _, _)| rgb).unwrap_or(default_rgb);
213 let mut connections = [None; 8];
214 let mut has_connections = false;
215 let river_width = river_kind.map(|river| match river {
218 RiverKind::River { cross_section } => cross_section.x,
219 RiverKind::Lake { .. } | RiverKind::Ocean => TerrainChunkSize::RECT_SIZE.x as f32,
220 });
221 if let (Some(river_width), true) = (river_width, is_water) {
222 let downhill_pos = downhill_wpos.wpos_to_cpos();
223 NEIGHBOR_DELTA
224 .iter()
225 .zip(connections.iter_mut())
226 .filter(|&(&offset, _)| downhill_pos - pos == Vec2::from(offset))
227 .for_each(|(_, connection)| {
228 has_connections = true;
229 *connection = Some(Connection {
230 kind: ConnectionKind::River,
231 spline_derivative,
232 width: river_width,
233 });
234 });
235 };
236 let rgb = if is_water && is_ice && column_data.is_some_and(|(_, _, ice_depth)| ice_depth > 0.0)
237 {
238 CONFIG.ice_color
239 } else {
240 match (river_kind, (is_water, true_alt >= true_sea_level)) {
241 (_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => {
242 let (r, g, b) = (
243 (column_rgb.r
244 * if is_temperature {
245 temperature as f64
246 } else {
247 column_rgb.r
248 })
249 .sqrt(),
250 column_rgb.g,
251 (column_rgb.b
252 * if is_humidity {
253 humidity as f64
254 } else {
255 column_rgb.b
256 })
257 .sqrt(),
258 );
259 Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
260 },
261 (None | Some(RiverKind::Lake { .. } | RiverKind::Ocean), _) => Rgb::new(
262 0,
263 ((g_water - water_depth * g_water) * 1.0) as u8,
264 ((b_water - water_depth * b_water) * 1.0) as u8,
265 ),
266 }
267 };
268 let rgb = if is_bridge {
270 Rgb::new(0x80, 0x80, 0x80)
271 } else if is_path {
272 Rgb::new(0x37, 0x29, 0x23)
273 } else {
274 rgb
275 };
276
277 MapSample {
278 rgb: Rgb::new(rgb.r, rgb.g, rgb.b),
279 alt: if is_water {
280 true_alt.max(true_water_alt)
281 } else {
282 true_alt
283 },
284 downhill_wpos,
285 connections: if has_connections {
286 Some(connections)
287 } else {
288 None
289 },
290 }
291}