use crate::{ column::ColumnSample, sim::{RiverKind, WorldSim}, CONFIG, }; use common::{ terrain::{ map::{Connection, ConnectionKind, MapConfig, MapSample}, vec2_as_uniform_idx, TerrainChunkSize, NEIGHBOR_DELTA, }, vol::RectVolSize, }; use std::{f32, f64}; use vek::*; /// A sample function that grabs the connections at a chunk. /// /// Currently this just supports rivers, but ideally it can be extended past /// that. /// /// A sample function that grabs surface altitude at a column. /// (correctly reflecting settings like is_basement and is_water). /// /// The altitude produced by this function at a column corresponding to a /// particular chunk should be identical to the altitude produced by /// sample_pos at that chunk. /// /// You should generally pass a closure over this function into generate /// when constructing a map for the first time. /// However, if repeated construction is needed, or alternate base colors /// are to be used for some reason, one should pass a custom function to /// generate instead (e.g. one that just looks up the height in a cached /// array). pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2) -> f32 { let MapConfig { focus, gain, is_basement, is_water, .. } = *config; (sampler .get_wpos(wpos) .map(|s| { if is_basement { s.basement } else { s.alt }.max(if is_water { s.water_alt } else { -f32::INFINITY }) }) .unwrap_or(CONFIG.sea_level) - focus.z as f32) / gain as f32 } /// Samples a MapSample at a chunk. /// /// You should generally pass a closure over this function into generate /// when constructing a map for the first time. /// However, if repeated construction is needed, or alternate base colors /// are to be used for some reason, one should pass a custom function to /// generate instead (e.g. one that just looks up the color in a cached /// array). // NOTE: Deliberately not putting Rgb colors here in the config file; they // aren't hot reloaded anyway, and for various reasons they're probably not a // good idea to update in that way (for example, we currently want water colors // to match voxygen's). Eventually we'll fix these sorts of issues in some // other way. pub fn sample_pos( config: &MapConfig, sampler: &WorldSim, samples: Option<&[Option]>, pos: Vec2, ) -> MapSample { let map_size_lg = config.map_size_lg(); let MapConfig { focus, gain, is_basement, is_water, is_shaded, is_temperature, is_humidity, // is_debug, .. } = *config; let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64; let ( chunk_idx, alt, basement, water_alt, humidity, temperature, downhill, river_kind, spline_derivative, is_path, ) = sampler .get(pos) .map(|sample| { ( Some(vec2_as_uniform_idx(map_size_lg, pos)), sample.alt, sample.basement, sample.water_alt, sample.humidity, sample.temp, sample.downhill, sample.river.river_kind, sample.river.spline_derivative, sample.path.0.is_way(), ) }) .unwrap_or(( None, CONFIG.sea_level, CONFIG.sea_level, CONFIG.sea_level, 0.0, 0.0, None, None, Vec2::zero(), false, )); let humidity = humidity.min(1.0).max(0.0); let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let column_rgb_alt = samples .and_then(|samples| { chunk_idx .and_then(|chunk_idx| samples.get(chunk_idx)) .map(Option::as_ref) .flatten() }) .map(|sample| { // TODO: Eliminate the redundancy between this and the block renderer. let alt = sample.alt; let basement = sample.basement; let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement); let wposz = if is_basement { basement } else { alt }; let rgb = if is_basement && wposz < alt - grass_depth { Lerp::lerp( sample.sub_surface_color, sample.stone_col.map(|e| e as f32 / 255.0), (alt - grass_depth - wposz as f32) * 0.15, ) .map(|e| e as f64) } else { Lerp::lerp( sample.sub_surface_color, sample.surface_color, ((wposz as f32 - (alt - grass_depth)) / grass_depth).sqrt(), ) .map(|e| e as f64) }; (rgb, alt) }); let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)); let alt = if is_basement { basement } else { column_rgb_alt.map_or(alt, |(_, alt)| alt) }; let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64; let true_alt = (alt as f64 - focus.z) / gain as f64; let water_depth = (true_water_alt - true_alt).min(1.0).max(0.0); let alt = true_alt.min(1.0).max(0.0); let water_color_factor = 2.0; let g_water = 32.0 * water_color_factor; let b_water = 64.0 * water_color_factor; let default_rgb = Rgb::new( if is_shaded || is_temperature { 1.0 } else { 0.0 }, if is_shaded { 1.0 } else { alt }, if is_shaded || is_humidity { 1.0 } else { 0.0 }, ); let column_rgb = column_rgb_alt.map(|(rgb, _)| rgb).unwrap_or(default_rgb); let mut connections = [None; 8]; let mut has_connections = false; // TODO: Support non-river connections. // TODO: Support multiple connections. let river_width = river_kind.map(|river| match river { RiverKind::River { cross_section } => cross_section.x, RiverKind::Lake { .. } | RiverKind::Ocean => TerrainChunkSize::RECT_SIZE.x as f32, }); if let (Some(river_width), true) = (river_width, is_water) { let downhill_pos = downhill_wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32); NEIGHBOR_DELTA .iter() .zip((&mut connections).iter_mut()) .filter(|&(&offset, _)| downhill_pos - pos == Vec2::from(offset)) .for_each(|(_, connection)| { has_connections = true; *connection = Some(Connection { kind: ConnectionKind::River, spline_derivative, width: river_width, }); }); }; let rgb = match (river_kind, (is_water, true_alt >= true_sea_level)) { (_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => { let (r, g, b) = ( (column_rgb.r * if is_temperature { temperature as f64 } else { column_rgb.r }) .sqrt(), column_rgb.g, (column_rgb.b * if is_humidity { humidity as f64 } else { column_rgb.b }) .sqrt(), ); Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8) }, (None | Some(RiverKind::Lake { .. } | RiverKind::Ocean), _) => Rgb::new( 0, ((g_water - water_depth * g_water) * 1.0) as u8, ((b_water - water_depth * b_water) * 1.0) as u8, ), }; // TODO: Make principled. let rgb = if is_path { Rgb::new(0x37, 0x29, 0x23) } else { rgb }; MapSample { rgb: Rgb::new(rgb.r, rgb.g, rgb.b), alt: if is_water { true_alt.max(true_water_alt) } else { true_alt }, downhill_wpos, connections: if has_connections { Some(connections) } else { None }, } }