diff --git a/Cargo.toml b/Cargo.toml index 2c7b8a0bbe..362bccde47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ debug = false codegen-units = 8 lto = false # TEMP false to avoid fingerprints bug -incremental = false +incremental = true # All dependencies (but not this crate itself) [profile.dev.package."*"] opt-level = 3 diff --git a/client/src/lib.rs b/client/src/lib.rs index 0c2a1b59fb..6d34132d5d 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -34,7 +34,7 @@ use common::{ outcome::Outcome, recipe::RecipeBook, resources::{DeltaTime, PlayerEntity, TimeOfDay}, - terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, + terrain::{block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, uid::{Uid, UidAllocator}, vol::RectVolSize, @@ -112,21 +112,22 @@ pub struct WorldData { /// map data (e.g. with shadow map data or river data), but at present /// we opt not to do this. /// - /// The second element of the tuple is the world size (as a 2D grid, - /// in chunks), and the third element holds the minimum height for any land - /// chunk (i.e. the sea level) in its x coordinate, and the maximum land - /// height above this height (i.e. the max height) in its y coordinate. - map: (Arc, Vec2, Vec2), + /// The first two elements of the tuple are the regular and topographic maps + /// respectively. The third element of the tuple is the world size (as a 2D + /// grid, in chunks), and the fourth element holds the minimum height for + /// any land chunk (i.e. the sea level) in its x coordinate, and the maximum + /// land height above this height (i.e. the max height) in its y coordinate. + map: (Arc, Arc, Vec2, Vec2), } impl WorldData { - pub fn chunk_size(&self) -> Vec2 { self.map.1 } + pub fn chunk_size(&self) -> Vec2 { self.map.2 } pub fn map_image(&self) -> &Arc { &self.map.0 } - pub fn min_chunk_alt(&self) -> f32 { self.map.2.x } + pub fn map_topo_image(&self) -> &Arc { &self.map.1 } - pub fn max_chunk_alt(&self) -> f32 { self.map.2.y } + pub fn max_chunk_alt(&self) -> f32 { self.map.3.y } } pub struct SiteInfoRich { @@ -324,6 +325,7 @@ impl Client { // Redraw map (with shadows this time). let mut world_map_rgba = vec![0u32; rgba.size().product() as usize]; + let mut world_map_topo = vec![0u32; rgba.size().product() as usize]; let mut map_config = common::terrain::map::MapConfig::orthographic( map_size_lg, core::ops::RangeInclusive::new(0.0, max_height), @@ -336,18 +338,40 @@ impl Client { && pos.y < map_size.y as i32 }; ping_stream.send(PingMsg::Ping)?; - map_config.generate( - |pos| { + fn sample_pos(map_config: &MapConfig, pos: Vec2, alt: &Grid, rgba: &Grid, map_size: &Vec2, map_size_lg: &common::terrain::MapSizeLg, max_height: f32) -> common::terrain::map::MapSample { + let rescale_height = |h: f32| h / max_height; + let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height; + let bounds_check = |pos: Vec2| { + pos.reduce_partial_min() >= 0 + && pos.x < map_size.x as i32 + && pos.y < map_size.y as i32 + }; + let MapConfig { + gain, + is_contours, + is_hill_shaded, + .. + } = *map_config; + let mut is_contour_line = false; let (rgba, alt, downhill_wpos) = if bounds_check(pos) { let posi = pos.y as usize * map_size.x as usize + pos.x as usize; let [r, g, b, a] = rgba[pos].to_le_bytes(); let alti = alt[pos]; + // Compute contours (chunks are assigned in the river code below) + let altj = rescale_height(scale_height_big(alti)); + let chunk_contour = (altj * gain / 100.0) as u32; + // Compute downhill. let downhill = { let mut best = -1; let mut besth = alti; - for nposi in neighbors(map_size_lg, posi) { + for nposi in neighbors(*map_size_lg, posi) { let nbh = alt.raw()[nposi]; + let nalt = rescale_height(scale_height_big(nbh)); + let nchunk_contour = (nalt * gain / 100.0) as u32; + if chunk_contour > nchunk_contour { + is_contour_line = true; + } if nbh < besth { besth = nbh; best = nposi as isize; @@ -369,17 +393,43 @@ impl Client { } else { (Rgba::zero(), 0, None) }; + let alt = f64::from(rescale_height(scale_height_big(alt))); let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let downhill_wpos = downhill_wpos .unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)); - let alt = rescale_height(scale_height_big(alt)); + let is_path = rgba.r == 0x37 && rgba.g == 0x29 && rgba.b == 0x23; + let rgb = Rgb::from(rgba).map(|e: u8| e as f64 / 255.0); + let rgb = if is_hill_shaded { + if is_path { + // Path color is Rgb::new(0x37, 0x29, 0x23) + Rgb::new(0.9, 0.9, 0.63) + } else if rgb.r == 0.0 && rgb.b > 0.4 && rgb.g < 0.3 { + // Water + Rgb::new(0.23, 0.47, 0.53) + } else if is_contours && is_contour_line { + // Color contour lines + Rgb::new(0.15, 0.15, 0.15) + } else { + // Color hill shading + let lightness = (alt + 0.2).min(1.0) as f64; + Rgb::new(lightness, 0.9 * lightness, 0.5 * lightness) + } + } else if is_contours && is_contour_line { + // Color contour lines + Rgb::new(0.15, 0.15, 0.15) + } else { + rgb + }.map(|e| (e * 255.0) as u8); common::terrain::map::MapSample { - rgb: Rgb::from(rgba), - alt: f64::from(alt), + rgb, + alt, downhill_wpos, connections: None, + is_path, } - }, + } + map_config.generate( + |pos| sample_pos(&map_config, pos, &alt, &rgba, &map_size, &map_size_lg, max_height), |wpos| { let pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32); rescale_height(if bounds_check(pos) { @@ -393,6 +443,26 @@ impl Client { u32::from_le_bytes([r, g, b, a]); }, ); + + // Generate topographic map + map_config.is_hill_shaded = true; + map_config.is_contours = true; + map_config.is_shaded = false; + map_config.generate( + |pos| sample_pos(&map_config, pos, &alt, &rgba, &map_size, &map_size_lg, max_height), + |wpos| { + let pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32); + rescale_height(if bounds_check(pos) { + scale_height_big(alt[pos]) + } else { + 0.0 + }) + }, + |pos, (r, g, b, a)| { + world_map_topo[pos.y * map_size.x as usize + pos.x] = + u32::from_le_bytes([r, g, b, a]); + }, + ); ping_stream.send(PingMsg::Ping)?; let make_raw = |rgba| -> Result<_, Error> { let mut raw = vec![0u8; 4 * world_map_rgba.len()]; @@ -413,6 +483,7 @@ impl Client { let lod_base = rgba; let lod_alt = alt; let world_map_img = make_raw(&world_map_rgba)?; + let world_map_topo_img = make_raw(&world_map_topo)?; let horizons = (west.0, west.1, east.0, east.1) .into_par_iter() .map(|(wa, wh, ea, eh)| u32::from_le_bytes([wa, wh, ea, eh])) @@ -426,7 +497,7 @@ impl Client { lod_base, lod_alt, Grid::from_raw(map_size.map(|e| e as i32), lod_horizon), - (world_map_img, map_size, map_bounds), + (world_map_img, world_map_topo_img, map_size, map_bounds), world_map.sites, recipe_book, max_group_size, diff --git a/common/src/terrain/map.rs b/common/src/terrain/map.rs index dcde0db0e3..587b34fec9 100644 --- a/common/src/terrain/map.rs +++ b/common/src/terrain/map.rs @@ -317,6 +317,16 @@ pub struct MapConfig<'a> { /// /// Defaults to false. pub is_debug: bool, + /// If true, contour lines are drawn on top of the base rbg + /// + /// Defaults to false. + pub is_contours: bool, + /// If true, hill shading is applied to the terrain and all the + /// colors are different. Is incompatible with humidity/temperature/shaded + /// maps. + /// + /// Defaults to false + pub is_hill_shaded: bool, } pub const QUADRANTS: usize = 4; @@ -366,6 +376,8 @@ pub struct MapSample { /// Connections at each index correspond to the same index in /// NEIGHBOR_DELTA. pub connections: Option<[Option; 8]>, + /// If the chunk contains a path + pub is_path: bool, } impl<'a> MapConfig<'a> { @@ -395,6 +407,8 @@ impl<'a> MapConfig<'a> { is_temperature: false, is_humidity: false, is_debug: false, + is_contours: false, + is_hill_shaded: false, } } @@ -432,7 +446,6 @@ impl<'a> MapConfig<'a> { scale, light_direction, horizons, - is_shaded, // is_debug, .. @@ -712,3 +725,4 @@ impl<'a> MapConfig<'a> { } } } + diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 5cda0b59c1..2e8588e7f2 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -72,6 +72,7 @@ const SHOW_ECONOMY: bool = false; // turn this display off (for 0.9) until we ha pub struct Map<'a> { client: &'a Client, world_map: &'a (img_ids::Rotations, Vec2), + world_map_topo: &'a (img_ids::Rotations, Vec2), imgs: &'a Imgs, fonts: &'a Fonts, #[conrod(common_builder)] @@ -89,6 +90,7 @@ impl<'a> Map<'a> { imgs: &'a Imgs, rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), + world_map_topo: &'a (img_ids::Rotations, Vec2), fonts: &'a Fonts, pulse: f32, localized_strings: &'a Localization, @@ -99,6 +101,7 @@ impl<'a> Map<'a> { imgs, rot_imgs, world_map, + world_map_topo, client, fonts, common: widget::CommonBuilder::default(), @@ -123,6 +126,7 @@ pub enum Event { ShowDungeons(bool), ShowCaves(bool), ShowTrees(bool), + ShowTopoMap(bool), Close, RequestSiteInfo(SiteId), } @@ -184,6 +188,7 @@ impl<'a> Widget for Map<'a> { let show_castles = self.global_state.settings.interface.map_show_castles; let show_caves = self.global_state.settings.interface.map_show_caves; let show_trees = self.global_state.settings.interface.map_show_trees; + let show_topo_map = self.global_state.settings.interface.map_show_topo_map; let mut events = Vec::new(); let i18n = &self.localized_strings; // Tooltips @@ -271,7 +276,7 @@ impl<'a> Widget for Map<'a> { .parent(state.ids.bg) .set(state.ids.grid, ui); // Map Image - let (world_map, worldsize) = self.world_map; + let (world_map, worldsize) = if !show_topo_map { self.world_map } else { self.world_map_topo }; // Coordinates let player_pos = self @@ -539,6 +544,40 @@ impl<'a> Widget for Map<'a> { { events.push(Event::ShowTrees(!show_trees)); } + Text::new(i18n.get("hud.map.trees")) + .right_from(state.ids.show_trees_box, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_trees_box) + .color(TEXT_COLOR) + .set(state.ids.show_trees_text, ui); + // Topographical Map + Image::new(self.imgs.mmap_site_tree) + .down_from(state.ids.show_caves_img, 10.0) + .w_h(20.0, 20.0) + .set(state.ids.show_trees_img, ui); + if Button::image(if show_topo_map { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox + }) + .w_h(18.0, 18.0) + .hover_image(if show_topo_map { + self.imgs.checkbox_checked_mo + } else { + self.imgs.checkbox_mo + }) + .press_image(if show_topo_map { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox_press + }) + .right_from(state.ids.show_trees_img, 10.0) + .set(state.ids.show_trees_box, ui) + .was_clicked() + { + events.push(Event::ShowTopoMap(!show_topo_map)); + } Text::new(i18n.get("hud.map.trees")) .right_from(state.ids.show_trees_box, 10.0) .font_size(self.fonts.cyri.scale(14)) diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index ceee112fba..3107e4afce 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -48,6 +48,7 @@ pub struct MiniMap<'a> { imgs: &'a Imgs, rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), + world_map_topo: &'a (img_ids::Rotations, Vec2), fonts: &'a Fonts, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -61,6 +62,7 @@ impl<'a> MiniMap<'a> { imgs: &'a Imgs, rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), + world_map_topo: &'a (img_ids::Rotations, Vec2), fonts: &'a Fonts, ori: Vec3, global_state: &'a GlobalState, @@ -70,6 +72,7 @@ impl<'a> MiniMap<'a> { imgs, rot_imgs, world_map, + world_map_topo, fonts, common: widget::CommonBuilder::default(), ori, @@ -140,7 +143,8 @@ impl<'a> Widget for MiniMap<'a> { .set(state.ids.mmap_frame_bg, ui); // Map size in chunk coords - let (world_map, worldsize) = self.world_map; + let show_topo_map = self.global_state.settings.interface.map_show_topo_map; + let (world_map, worldsize) = if !show_topo_map { self.world_map } else { self.world_map_topo }; // Zoom Buttons diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index acd945ae14..516de25c5d 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -250,6 +250,7 @@ widget_ids! { chat, map, world_map, + world_map_topo, character_window, popup, minimap, @@ -380,6 +381,7 @@ pub enum Event { MapShowCastles(bool), MapShowCaves(bool), MapShowTrees(bool), + MapShowTopo(bool), AdjustWindowSize([u16; 2]), ChangeFullscreenMode(FullScreenSettings), ToggleParticlesEnabled(bool), @@ -778,6 +780,7 @@ pub struct Hud { ui: Ui, ids: Ids, world_map: (/* Id */ Rotations, Vec2), + world_map_topo: (/* Id */ Rotations, Vec2), imgs: Imgs, item_imgs: ItemImgs, fonts: Fonts, @@ -825,6 +828,14 @@ impl Hud { )), client.world_data().chunk_size().map(|e| e as u32), ); + // Load world topo map + let world_map_topo = ( + ui.add_graphic_with_rotations(Graphic::Image( + Arc::clone(client.world_data().map_topo_image()), + Some(water_color), + )), + client.world_data().chunk_size().map(|e| e as u32), + ); // Load images. let imgs = Imgs::load(&mut ui).expect("Failed to load images!"); // Load rotation images. @@ -862,6 +873,7 @@ impl Hud { ui, imgs, world_map, + world_map_topo, rot_imgs, item_imgs, fonts, @@ -2230,6 +2242,7 @@ impl Hud { &self.imgs, &self.rot_imgs, &self.world_map, + &self.world_map_topo, &self.fonts, camera.get_orientation(), &global_state, @@ -2783,6 +2796,7 @@ impl Hud { &self.imgs, &self.rot_imgs, &self.world_map, + &self.world_map_topo, &self.fonts, self.pulse, i18n, @@ -2821,6 +2835,9 @@ impl Hud { map::Event::ShowTrees(map_show_trees) => { events.push(Event::MapShowTrees(map_show_trees)); }, + map::Event::ShowTopoMap(map_show_topo_map) => { + events.push(Event::MapShowTopo(map_show_topo_map)); + }, map::Event::RequestSiteInfo(id) => { events.push(Event::RequestSiteInfo(id)); }, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index ff7c946075..c6d9a1c924 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1356,6 +1356,10 @@ impl PlayState for SessionState { global_state.settings.interface.map_show_trees = map_show_trees; global_state.settings.save_to_file_warn(); }, + HudEvent::MapShowTopo(map_show_topo) => { + global_state.settings.interface.map_show_topo_map = map_show_topo; + global_state.settings.save_to_file_warn(); + }, HudEvent::RequestSiteInfo(id) => { let mut client = self.client.borrow_mut(); client.request_site_economy(id); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 55dcd9851a..311920c5e9 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -451,6 +451,7 @@ pub struct InterfaceSettings { pub loading_tips: bool, pub map_show_caves: bool, pub map_show_trees: bool, + pub map_show_topo_map: bool, pub minimap_show: bool, pub minimap_face_north: bool, } @@ -483,6 +484,7 @@ impl Default for InterfaceSettings { loading_tips: true, map_show_caves: true, map_show_trees: true, + map_show_topo_map: false, minimap_show: true, minimap_face_north: false, } diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 8ac6747b97..8f03a53263 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -260,5 +260,6 @@ pub fn sample_pos( } else { None }, + is_path, } }