Merge branch 'james/topo-map' into 'master'

Adds topographic map option and allows for map layers

See merge request veloren/veloren!2074
This commit is contained in:
Marcel 2021-04-07 09:34:13 +00:00
commit 45b4349bc7
13 changed files with 453 additions and 117 deletions

View File

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Attacks now emit sound effects from the target on hit.
- Crafting menu tabs
- Auto camera setting, making the game easier to play with one hand
- Topographic map option
### Changed

BIN
assets/voxygen/element/map/topographic.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -6,6 +6,7 @@
// Map and Questlog
"hud.map.map_title": "Map",
"hud.map.qlog_title": "Quests",
"hud.map.topo_map": "Topographic",
"hud.map.difficulty": "Difficulty",
"hud.map.towns": "Towns",
"hud.map.castles": "Castles",

View File

@ -34,7 +34,10 @@ 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,17 +115,20 @@ 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<DynamicImage>, Vec2<u16>, Vec2<f32>),
/// 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: (Vec<Arc<DynamicImage>>, Vec2<u16>, Vec2<f32>),
}
impl WorldData {
pub fn chunk_size(&self) -> Vec2<u16> { self.map.1 }
pub fn map_image(&self) -> &Arc<DynamicImage> { &self.map.0 }
pub fn map_layers(&self) -> &Vec<Arc<DynamicImage>> { &self.map.0 }
pub fn map_image(&self) -> &Arc<DynamicImage> { &self.map.0[0] }
pub fn min_chunk_alt(&self) -> f32 { self.map.2.x }
@ -324,6 +330,9 @@ impl Client {
// Redraw map (with shadows this time).
let mut world_map_rgba = vec![0u32; rgba.size().product() as usize];
let mut world_map_political = vec![0u32; rgba.size().product() as usize];
let mut world_map_rgba_half_alpha = 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,49 +345,140 @@ impl Client {
&& pos.y < map_size.y as i32
};
ping_stream.send(PingMsg::Ping)?;
fn sample_pos(
map_config: &MapConfig,
pos: Vec2<i32>,
alt: &Grid<u32>,
rgba: &Grid<u32>,
map_size: &Vec2<u16>,
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<i32>| {
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_height_map,
is_political,
is_roads,
rgba_alpha,
..
} = *map_config;
let mut is_contour_line = false;
let mut is_border = 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 is_water = r == 0 && b > 102 && g < 77;
let alti = alt[pos];
// Compute contours (chunks are assigned in the river code below)
let altj = rescale_height(scale_height_big(alti));
let contour_interval = 150.0;
let chunk_contour = (altj * gain / contour_interval) as u32;
// Compute downhill.
let downhill = {
let mut best = -1;
let mut besth = alti;
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 / contour_interval) as u32;
if !is_contour_line && chunk_contour > nchunk_contour {
is_contour_line = true;
}
let [nr, ng, nb, _na] = rgba.raw()[nposi].to_le_bytes();
let n_is_water = nr == 0 && nb > 102 && ng < 77;
if !is_border && is_political && is_water && !n_is_water {
is_border = true;
}
if nbh < besth {
besth = nbh;
best = nposi as isize;
}
}
best
};
let downhill_wpos = if downhill < 0 {
None
} else {
Some(
Vec2::new(
(downhill as usize % map_size.x as usize) as i32,
(downhill as usize / map_size.x as usize) as i32,
) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
)
};
(Rgba::new(r, g, b, a), alti, downhill_wpos)
} 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 is_path = rgba.r == 0x37 && rgba.g == 0x29 && rgba.b == 0x23;
let rgba = rgba.map(|e: u8| e as f64 / 255.0);
let rgba = if is_height_map {
if is_path {
// Path color is Rgb::new(0x37, 0x29, 0x23)
Rgba::new(0.9, 0.9, 0.63, 1.0)
} else if rgba.r == 0.0 && rgba.b > 0.4 && rgba.g < 0.3 {
// Water
Rgba::new(0.23, 0.47, 0.53, 0.5)
} else if is_contours && is_contour_line {
// Color contour lines
Rgba::new(0.15, 0.15, 0.15, 0.5)
} else {
// Color hill shading
let lightness = (alt + 0.2).min(1.0) as f64;
Rgba::new(lightness, 0.9 * lightness, 0.5 * lightness, 0.5)
}
} else if is_roads && is_path {
Rgba::new(0.9, 0.9, 0.63, 1.0)
} else if is_political {
if is_path {
Rgba::new(0.3, 0.3, 0.3, 1.0)
} else if is_border {
Rgba::new(0.0, 0.0, 0.0, 1.0)
} else {
Rgba::new(1.0, 0.9, 0.6, 1.0)
}
} else if is_contours && is_contour_line {
Rgba::new(0.15, 0.15, 0.15, 0.8)
} else {
Rgba::new(rgba.r, rgba.g, rgba.b, rgba_alpha)
}
.map(|e| (e * 255.0) as u8);
common::terrain::map::MapSample {
rgba,
alt,
downhill_wpos,
connections: None,
is_path,
}
}
map_config.is_shaded = true;
map_config.rgba_alpha = 1.0;
map_config.generate(
|pos| {
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 downhill.
let downhill = {
let mut best = -1;
let mut besth = alti;
for nposi in neighbors(map_size_lg, posi) {
let nbh = alt.raw()[nposi];
if nbh < besth {
besth = nbh;
best = nposi as isize;
}
}
best
};
let downhill_wpos = if downhill < 0 {
None
} else {
Some(
Vec2::new(
(downhill as usize % map_size.x as usize) as i32,
(downhill as usize / map_size.x as usize) as i32,
) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
)
};
(Rgba::new(r, g, b, a), alti, downhill_wpos)
} else {
(Rgba::zero(), 0, None)
};
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));
common::terrain::map::MapSample {
rgb: Rgb::from(rgba),
alt: f64::from(alt),
downhill_wpos,
connections: None,
}
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);
@ -393,6 +493,89 @@ impl Client {
u32::from_le_bytes([r, g, b, a]);
},
);
map_config.is_political = true;
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_political[pos.y * map_size.x as usize + pos.x] =
u32::from_le_bytes([r, g, b, a]);
},
);
map_config.is_shaded = false;
map_config.rgba_alpha = 0.5;
map_config.is_political = 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_rgba_half_alpha[pos.y * map_size.x as usize + pos.x] =
u32::from_le_bytes([r, g, b, a]);
},
);
// Generate topographic map
map_config.is_contours = true;
map_config.is_roads = true;
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()];
@ -412,7 +595,16 @@ impl Client {
ping_stream.send(PingMsg::Ping)?;
let lod_base = rgba;
let lod_alt = alt;
let world_map_img = make_raw(&world_map_rgba)?;
let world_map_rgba_img = make_raw(&world_map_rgba)?;
let world_map_political_img = make_raw(&world_map_political)?;
let world_map_rgba_half_alpha_img = make_raw(&world_map_rgba_half_alpha)?;
let world_map_topo_img = make_raw(&world_map_topo)?;
let world_map_layers = vec![
world_map_rgba_img,
world_map_political_img,
world_map_rgba_half_alpha_img,
world_map_topo_img,
];
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 +618,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_layers, map_size, map_bounds),
world_map.sites,
recipe_book,
max_group_size,

View File

@ -317,6 +317,27 @@ 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, a yellow/terracotta heightmap shading is applied to the
/// terrain and water is a faded blue.
///
/// Defaults to false
pub is_height_map: bool,
/// If true, terrain is white, rivers, borders, and roads are black.
///
/// Defaults to false
pub is_political: bool,
/// If true, roads are colored on top of everything else
///
/// Defaults to false
pub is_roads: bool,
/// Alpha value for rgba. Handled by the sample_pos closure
///
/// Defaults to 1.0
pub rgba_alpha: f64,
}
pub const QUADRANTS: usize = 4;
@ -353,7 +374,7 @@ pub struct Connection {
pub struct MapSample {
/// the base RGB color for a particular map pixel using the current settings
/// (i.e. the color *without* lighting).
pub rgb: Rgb<u8>,
pub rgba: Rgba<u8>,
/// Surface altitude information
/// (correctly reflecting settings like is_basement and is_water)
pub alt: f64,
@ -366,6 +387,8 @@ pub struct MapSample {
/// Connections at each index correspond to the same index in
/// NEIGHBOR_DELTA.
pub connections: Option<[Option<Connection>; 8]>,
/// If the chunk contains a path
pub is_path: bool,
}
impl<'a> MapConfig<'a> {
@ -395,6 +418,11 @@ impl<'a> MapConfig<'a> {
is_temperature: false,
is_humidity: false,
is_debug: false,
is_contours: false,
is_height_map: false,
is_political: false,
is_roads: false,
rgba_alpha: 1.0,
}
}
@ -432,7 +460,6 @@ impl<'a> MapConfig<'a> {
scale,
light_direction,
horizons,
is_shaded,
// is_debug,
..
@ -484,7 +511,7 @@ impl<'a> MapConfig<'a> {
};
let MapSample {
rgb,
rgba,
alt,
downhill_wpos,
..
@ -492,7 +519,8 @@ impl<'a> MapConfig<'a> {
let alt = alt as f32;
let wposi = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
let mut rgb = rgb.map(|e| e as f64 / 255.0);
let rgb = Rgb::new(rgba.r, rgba.g, rgba.b).map(|e| e as f64 / 255.0);
let mut rgba = rgba.map(|e| e as f64 / 255.0);
// Material properties:
//
@ -568,7 +596,7 @@ impl<'a> MapConfig<'a> {
if has_river {
let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8)
.map(|e| e as f64 / 255.0);
rgb = water_rgb;
rgba = Rgba::new(water_rgb.r, water_rgb.g, water_rgb.b, rgba.a);
k_s = Rgb::new(1.0, 1.0, 1.0);
k_d = water_rgb;
k_a = water_rgb;
@ -694,13 +722,14 @@ impl<'a> MapConfig<'a> {
let ambient = k_a * i_a;
let diffuse = k_d * lambertian * i_m_d;
let specular = k_s * spec_angle.powf(alpha) * i_m_s;
(ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0))
let shadow_rgb = (ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0));
Rgba::new(shadow_rgb.r, shadow_rgb.g, shadow_rgb.b, 1.0)
} else {
rgb
rgba
}
.map(|e| (e * 255.0) as u8);
let rgba = (rgb.r, rgb.g, rgb.b, 255);
let rgba = (rgb.r, rgb.g, rgb.b, rgb.a);
write_pixel(Vec2::new(i, j), rgba);
});

View File

@ -336,6 +336,7 @@ image_ids! {
crosshair_bg_pressed: "voxygen.element.misc_bg.crosshair_bg_pressed",
// Map
map_topo: "voxygen.element.map.topographic",
map_bg: "voxygen.element.misc_bg.map_bg",
map_frame: "voxygen.element.misc_bg.map_frame",
map_frame_art: "voxygen.element.misc_bg.map_frame_art",

View File

@ -31,7 +31,7 @@ widget_ids! {
location_name,
indicator,
indicator_overlay,
grid,
map_layers[],
map_title,
qlog_title,
zoom_slider,
@ -40,6 +40,9 @@ widget_ids! {
member_indicators[],
member_height_indicators[],
map_settings_align,
show_topo_map_img,
show_topo_map_box,
show_topo_map_text,
show_towns_img,
show_towns_box,
show_towns_text,
@ -71,7 +74,7 @@ const SHOW_ECONOMY: bool = false; // turn this display off (for 0.9) until we ha
#[derive(WidgetCommon)]
pub struct Map<'a> {
client: &'a Client,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
imgs: &'a Imgs,
fonts: &'a Fonts,
#[conrod(common_builder)]
@ -88,7 +91,7 @@ impl<'a> Map<'a> {
client: &'a Client,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
fonts: &'a Fonts,
pulse: f32,
localized_strings: &'a Localization,
@ -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
@ -265,13 +270,25 @@ impl<'a> Widget for Map<'a> {
.color(TEXT_COLOR)
.set(state.ids.location_name, ui),
}*/
// Map Layers
// It is assumed that there is at least one layer
if state.ids.map_layers.len() < self.world_map.0.len() {
state.update(|state| {
state
.ids
.map_layers
.resize(self.world_map.0.len(), &mut ui.widget_id_generator())
});
}
Image::new(self.imgs.map_frame_art)
.mid_top_with_margin_on(state.ids.map_align, 5.0)
.w_h(765.0, 765.0)
.parent(state.ids.bg)
.set(state.ids.grid, ui);
// Map Image
let (world_map, worldsize) = self.world_map;
.set(state.ids.map_layers[0], ui);
// Map Size
let worldsize = self.world_map.1;
// Coordinates
let player_pos = self
@ -292,7 +309,7 @@ impl<'a> Widget for Map<'a> {
// Handle dragging
let drag = self.global_state.settings.interface.map_drag;
let dragged: Vec2<f64> = ui
.widget_input(state.ids.grid)
.widget_input(state.ids.map_layers[0])
.drags()
.left()
.map(|drag| Vec2::<f64>::from(drag.delta_xy))
@ -320,15 +337,30 @@ impl<'a> Widget for Map<'a> {
{
events.push(Event::Close);
}
Image::new(world_map.none)
.mid_top_with_margin_on(state.ids.map_align, 10.0)
.w_h(map_size.x, map_size.y)
.parent(state.ids.bg)
.source_rectangle(rect_src)
.set(state.ids.grid, ui);
// Map Layer Images
for (index, layer) in self.world_map.0.iter().enumerate() {
if index == 0 {
Image::new(layer.none)
.mid_top_with_margin_on(state.ids.map_align, 10.0)
.w_h(map_size.x, map_size.y)
.parent(state.ids.bg)
.source_rectangle(rect_src)
.set(state.ids.map_layers[index], ui);
} else if show_topo_map {
Image::new(layer.none)
.mid_top_with_margin_on(state.ids.map_align, 10.0)
.w_h(map_size.x, map_size.y)
.parent(state.ids.bg)
.source_rectangle(rect_src)
.graphics_for(state.ids.map_layers[0])
.set(state.ids.map_layers[index], ui);
}
}
// Handle zooming with the mousewheel
let scrolled: f64 = ui
.widget_input(state.ids.grid)
.widget_input(state.ids.map_layers[0])
.scrolls()
.map(|scroll| scroll.y)
.sum();
@ -342,9 +374,44 @@ impl<'a> Widget for Map<'a> {
.top_right_with_margins_on(state.ids.frame, 55.0, 10.0)
.set(state.ids.map_settings_align, ui);
// Checkboxes
// Show topographic map
Image::new(self.imgs.map_topo)
.top_left_with_margins_on(state.ids.map_settings_align, 5.0, 5.0)
.w_h(20.0, 20.0)
.set(state.ids.show_topo_map_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_topo_map_img, 10.0)
.set(state.ids.show_topo_map_box, ui)
.was_clicked()
{
events.push(Event::ShowTopoMap(!show_topo_map));
}
Text::new(i18n.get("hud.map.topo_map"))
.right_from(state.ids.show_topo_map_box, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.show_topo_map_box)
.color(TEXT_COLOR)
.set(state.ids.show_topo_map_text, ui);
// Show difficulties
Image::new(self.imgs.map_dif_6)
.top_left_with_margins_on(state.ids.map_settings_align, 5.0, 5.0)
.down_from(state.ids.show_topo_map_img, 10.0)
.w_h(20.0, 20.0)
.set(state.ids.show_difficulty_img, ui);
if Button::image(if show_difficulty {
@ -576,6 +643,7 @@ impl<'a> Widget for Map<'a> {
// Convert to relative pixel coordinates from the center of the map
// Accounting for zooming
let rpos = rfpos.map2(map_size, |e, sz| e * sz as f32 * zoom as f32);
let rside = zoom * 6.0;
if rpos
.map2(map_size, |e, sz| e.abs() > sz as f32 / 2.0)
@ -610,11 +678,11 @@ impl<'a> Widget for Map<'a> {
SiteKind::Tree => self.imgs.mmap_site_tree,
})
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64),
)
.w_h(20.0 * 1.2, 20.0 * 1.2)
.w_h(rside * 1.2, rside * 1.2)
.hover_image(match &site.kind {
SiteKind::Town => self.imgs.mmap_site_town_hover,
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon_hover,
@ -673,16 +741,16 @@ impl<'a> Widget for Map<'a> {
_ => self.imgs.nothing,
})
.mid_top_with_margin_on(state.ids.mmap_site_icons[i], match difficulty {
5 => -12.0 * size,
_ => -4.0 * size,
5 => -2.0 * zoom * size,
_ => -1.0 * zoom * size,
})
.w(match difficulty {
5 => 12.0 * size,
_ => 4.0 * size * difficulty as f64,
5 => 2.0 * zoom * size,
_ => 1.0 * zoom * size * difficulty as f64,
})
.h(match difficulty {
5 => 12.0 * size,
_ => 4.0 * size,
5 => 2.0 * size * zoom,
_ => 1.0 * zoom * size,
})
.color(Some(match difficulty {
0 => QUALITY_LOW,
@ -785,7 +853,7 @@ impl<'a> Widget for Map<'a> {
_ => self.imgs.indicator_group,
})
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64),
)
@ -819,7 +887,7 @@ impl<'a> Widget for Map<'a> {
{
Image::new(self.rot_imgs.indicator_mmap_small.target_north)
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64),
)
@ -838,7 +906,7 @@ impl<'a> Widget for Map<'a> {
};
if Button::image(self.imgs.button)
.w_h(92.0, icon_size.y)
.mid_bottom_with_margin_on(state.ids.grid, -36.0)
.mid_bottom_with_margin_on(state.ids.map_layers[0], -36.0)
.hover_image(if recenter {
self.imgs.button_hover
} else {
@ -870,7 +938,7 @@ impl<'a> Widget for Map<'a> {
};
Image::new(self.imgs.m_move_ico)
.bottom_left_with_margins_on(state.ids.grid, -36.0, 0.0)
.bottom_left_with_margins_on(state.ids.map_layers[0], -36.0, 0.0)
.w_h(icon_size.x, icon_size.y)
.color(Some(UI_HIGHLIGHT_0))
.set(state.ids.drag_ico, ui);
@ -878,7 +946,7 @@ impl<'a> Widget for Map<'a> {
.right_from(state.ids.drag_ico, 5.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.grid)
.graphics_for(state.ids.map_layers[0])
.color(TEXT_COLOR)
.set(state.ids.drag_txt, ui);
Image::new(self.imgs.m_scroll_ico)
@ -890,7 +958,7 @@ impl<'a> Widget for Map<'a> {
.right_from(state.ids.zoom_ico, 5.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.grid)
.graphics_for(state.ids.map_layers[0])
.color(TEXT_COLOR)
.set(state.ids.zoom_txt, ui);

View File

@ -29,7 +29,7 @@ widget_ids! {
mmap_plus,
mmap_minus,
mmap_north_button,
grid,
map_layers[],
indicator,
mmap_north,
mmap_east,
@ -47,7 +47,7 @@ pub struct MiniMap<'a> {
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -60,7 +60,7 @@ impl<'a> MiniMap<'a> {
client: &'a Client,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
fonts: &'a Fonts,
ori: Vec3<f32>,
global_state: &'a GlobalState,
@ -118,6 +118,7 @@ impl<'a> Widget for MiniMap<'a> {
const SCALE: f64 = 1.5; // TODO Make this a setting
let show_minimap = self.global_state.settings.interface.minimap_show;
let is_facing_north = self.global_state.settings.interface.minimap_face_north;
let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
let orientation = if is_facing_north {
Vec3::new(0.0, 1.0, 0.0)
} else {
@ -140,7 +141,17 @@ 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 worldsize = self.world_map.1;
// Map Layers
// It is assumed that there is at least one layer
if state.ids.map_layers.len() < self.world_map.0.len() {
state.update(|state| {
state
.ids
.map_layers
.resize(self.world_map.0.len(), &mut ui.widget_id_generator())
});
}
// Zoom Buttons
@ -248,17 +259,30 @@ impl<'a> Widget for MiniMap<'a> {
let map_size = Vec2::new(170.0 * SCALE, 170.0 * SCALE);
// Map Image
let world_map_rotation = if is_facing_north {
world_map.none
} else {
world_map.source_north
};
Image::new(world_map_rotation)
.middle_of(state.ids.mmap_frame_bg)
.w_h(map_size.x, map_size.y)
.parent(state.ids.mmap_frame_bg)
.source_rectangle(rect_src)
.set(state.ids.grid, ui);
// Map Layer Images
for (index, layer) in self.world_map.0.iter().enumerate() {
let world_map_rotation = if is_facing_north {
layer.none
} else {
layer.source_north
};
if index == 0 {
Image::new(world_map_rotation)
.middle_of(state.ids.mmap_frame_bg)
.w_h(map_size.x, map_size.y)
.parent(state.ids.mmap_frame_bg)
.source_rectangle(rect_src)
.set(state.ids.map_layers[index], ui);
} else if show_topo_map {
Image::new(world_map_rotation)
.middle_of(state.ids.mmap_frame_bg)
.w_h(map_size.x, map_size.y)
.parent(state.ids.mmap_frame_bg)
.source_rectangle(rect_src)
.graphics_for(state.ids.map_layers[0])
.set(state.ids.map_layers[index], ui);
}
}
// Map icons
if state.ids.mmap_site_icons.len() < self.client.sites().len() {
@ -307,7 +331,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Tree => self.imgs.mmap_site_tree,
})
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64),
)
@ -327,7 +351,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Cave => Color::Rgba(1.0, 1.0, 1.0, 0.0),
SiteKind::Tree => Color::Rgba(1.0, 1.0, 1.0, 0.0),
}))
.parent(state.ids.grid)
.parent(state.ids.map_layers[3])
.set(state.ids.mmap_site_icons_bgs[i], ui);
Image::new(match &site.kind {
SiteKind::Town => self.imgs.mmap_site_town,
@ -398,7 +422,7 @@ impl<'a> Widget for MiniMap<'a> {
_ => self.imgs.indicator_group,
})
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64),
)
@ -416,7 +440,7 @@ impl<'a> Widget for MiniMap<'a> {
self.rot_imgs.indicator_mmap_small.none
};
Image::new(ind_rotation)
.middle_of(state.ids.grid)
.middle_of(state.ids.map_layers[0])
.w_h(32.0 * ind_scale, 37.0 * ind_scale)
.color(Some(UI_HIGHLIGHT_0))
.floating(true)
@ -436,7 +460,7 @@ impl<'a> Widget for MiniMap<'a> {
let pos = clamped * (map_size / 2.0 - 10.0);
Text::new(name)
.x_y_position_relative_to(
state.ids.grid,
state.ids.map_layers[0],
position::Relative::Scalar(pos.x),
position::Relative::Scalar(pos.y),
)

View File

@ -374,6 +374,7 @@ pub enum Event {
ChangeAmbiance(f32),
MapZoom(f64),
MapDrag(Vec2<f64>),
MapShowTopoMap(bool),
MapShowDifficulty(bool),
MapShowTowns(bool),
MapShowDungeons(bool),
@ -777,7 +778,7 @@ impl PromptDialogSettings {
pub struct Hud {
ui: Ui,
ids: Ids,
world_map: (/* Id */ Rotations, Vec2<u32>),
world_map: (/* Id */ Vec<Rotations>, Vec2<u32>),
imgs: Imgs,
item_imgs: ItemImgs,
fonts: Fonts,
@ -818,13 +819,13 @@ impl Hud {
// translucent alpha since UI have transparency and LOD doesn't).
let water_color = srgba_to_linear(Rgba::new(0.0, 0.18, 0.37, 1.0));
// Load world map
let world_map = (
ui.add_graphic_with_rotations(Graphic::Image(
Arc::clone(client.world_data().map_image()),
Some(water_color),
)),
client.world_data().chunk_size().map(|e| e as u32),
);
let mut layers = Vec::new();
for layer in client.world_data().map_layers() {
layers.push(
ui.add_graphic_with_rotations(Graphic::Image(Arc::clone(layer), Some(water_color))),
);
}
let world_map = (layers, 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.
@ -2797,6 +2798,9 @@ impl Hud {
self.show.want_grab = true;
self.force_ungrab = false;
},
map::Event::ShowTopoMap(map_show_topo_map) => {
events.push(Event::MapShowTopoMap(map_show_topo_map));
},
map::Event::ShowDifficulties(map_show_difficulties) => {
events.push(Event::MapShowDifficulty(map_show_difficulties));
},

View File

@ -1332,6 +1332,10 @@ impl PlayState for SessionState {
global_state.settings.interface.map_drag = map_drag;
global_state.settings.save_to_file_warn();
},
HudEvent::MapShowTopoMap(map_show_topo_map) => {
global_state.settings.interface.map_show_topo_map = map_show_topo_map;
global_state.settings.save_to_file_warn();
},
HudEvent::MapShowDifficulty(map_show_difficulty) => {
global_state.settings.interface.map_show_difficulty = map_show_difficulty;
global_state.settings.save_to_file_warn();

View File

@ -444,6 +444,7 @@ pub struct InterfaceSettings {
pub ui_scale: ScaleMode,
pub map_zoom: f64,
pub map_drag: Vec2<f64>,
pub map_show_topo_map: bool,
pub map_show_difficulty: bool,
pub map_show_towns: bool,
pub map_show_dungeons: bool,
@ -476,6 +477,7 @@ impl Default for InterfaceSettings {
ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()),
map_zoom: 10.0,
map_drag: Vec2 { x: 0.0, y: 0.0 },
map_show_topo_map: false,
map_show_difficulty: true,
map_show_towns: true,
map_show_dungeons: true,

View File

@ -91,9 +91,10 @@ fn main() {
} else {
MapSample {
alt: 0.0,
rgb: Rgb::new(0, 0, 0),
rgba: Rgba::new(0, 0, 0, 255),
connections: None,
downhill_wpos: (pos + 1) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
is_path: false,
}
}
};
@ -178,6 +179,11 @@ fn main() {
is_temperature,
is_humidity,
is_debug: true,
is_contours: false,
is_height_map: false,
is_political: false,
is_roads: false,
rgba_alpha: 1.0,
};
if samples_changed {

View File

@ -248,7 +248,7 @@ pub fn sample_pos(
};
MapSample {
rgb,
rgba: Rgba::new(rgb.r, rgb.g, rgb.b, 255),
alt: if is_water {
true_alt.max(true_water_alt)
} else {
@ -260,5 +260,6 @@ pub fn sample_pos(
} else {
None
},
is_path,
}
}