mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
265 lines
8.3 KiB
Rust
265 lines
8.3 KiB
Rust
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<i32>) -> 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<ColumnSample>]>,
|
|
pos: Vec2<i32>,
|
|
) -> 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
|
|
},
|
|
}
|
|
}
|