Hack to allow minimap rotation.

Currently it just always rotates towards the camera, but it wouldn't be
hard to create a config option that swaps out the rotation of the
indicator and the map.
This commit is contained in:
Monty Marz 2020-02-06 17:34:32 +00:00 committed by Joshua Yanovski
parent ed620fd70d
commit 93d7c67cdc
28 changed files with 465 additions and 284 deletions

View File

@ -10,8 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added music system - Added music system
- Added zoomable and rotatable minimap
- Added rotating orientation marker to main-map
### Changed ### Changed
- Brighter / higher contrast main-map
### Removed ### Removed

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/buttons/indicator_mmap_small.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/mmap.vox (Stored with Git LFS)

Binary file not shown.

View File

@ -20,7 +20,8 @@ void main() {
if (f_mode == uint(0)) { if (f_mode == uint(0)) {
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
// Image // Image
} else if (f_mode == uint(1)) { // HACK: bit 0 is set for both ordinary and north-facing images.
} else if ((f_mode & uint(1)) == uint(1)) {
tgt_color = f_color * texture(u_tex, f_uv); tgt_color = f_color * texture(u_tex, f_uv);
// 2D Geometry // 2D Geometry
} else if (f_mode == uint(2)) { } else if (f_mode == uint(2)) {

View File

@ -4,6 +4,7 @@
in vec2 v_pos; in vec2 v_pos;
in vec2 v_uv; in vec2 v_uv;
in vec2 v_center;
in vec4 v_color; in vec4 v_color;
in uint v_mode; in uint v_mode;
@ -19,15 +20,31 @@ flat out uint f_mode;
out vec4 f_color; out vec4 f_color;
void main() { void main() {
f_uv = v_uv;
f_color = v_color; f_color = v_color;
if (w_pos.w == 1.0) { if (w_pos.w == 1.0) {
f_uv = v_uv;
// Fixed scale In-game element // Fixed scale In-game element
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0); vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0);
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0); gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0);
} else if (v_mode == uint(3)) {
// HACK: North facing source rectangle.
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
f_uv = v_center + look_at * (v_uv - v_center);
gl_Position = vec4(v_pos, 0.0, 1.0);
} else if (v_mode == uint(5)) {
// HACK: North facing target rectangle.
f_uv = v_uv;
float aspect_ratio = screen_res.x / screen_res.y;
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
mat2 look_at = mat2(look_at_dir.y, -look_at_dir.x, look_at_dir.x, look_at_dir.y);
vec2 v_len = v_pos - v_center;
vec2 v_proj = look_at * vec2(v_len.x, v_len.y / aspect_ratio);
gl_Position = vec4(v_center + vec2(v_proj.x, v_proj.y * aspect_ratio), 0.0, 1.0);
} else { } else {
// Interface element // Interface element
f_uv = v_uv;
gl_Position = vec4(v_pos, 0.0, 1.0); gl_Position = vec4(v_pos, 0.0, 1.0);
} }
f_mode = v_mode; f_mode = v_mode;

View File

@ -8,6 +8,13 @@ rotation_image_ids! {
// Tooltip Test // Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge", tt_side: "voxygen/element/frames/tt_test_edge",
tt_corner: "voxygen/element/frames/tt_test_corner_tr", tt_corner: "voxygen/element/frames/tt_test_corner_tr",
//////////////////////////////////////////////////////////////////////////////////////////////////////
<VoxelPixArtGraphic>
// Minimap
indicator_mmap_small: "voxygen.element.buttons.indicator_mmap_small",
} }
} }
@ -131,7 +138,6 @@ image_ids! {
skull_2: "voxygen.element.icons.skull_2", skull_2: "voxygen.element.icons.skull_2",
// Map // Map
map_indicator: "voxygen.element.buttons.map_indicator",
indicator_mmap: "voxygen.element.buttons.indicator_mmap", indicator_mmap: "voxygen.element.buttons.indicator_mmap",
indicator_mmap_2: "voxygen.element.buttons.indicator_mmap_2", indicator_mmap_2: "voxygen.element.buttons.indicator_mmap_2",
indicator_mmap_3: "voxygen.element.buttons.indicator_mmap_3", indicator_mmap_3: "voxygen.element.buttons.indicator_mmap_3",
@ -169,7 +175,7 @@ image_ids! {
mmap_open_press: "voxygen.element.buttons.button_mmap_open_press", mmap_open_press: "voxygen.element.buttons.button_mmap_open_press",
mmap_plus: "voxygen.element.buttons.min_plus.mmap_button-plus", mmap_plus: "voxygen.element.buttons.min_plus.mmap_button-plus",
mmap_plus_hover: "voxygen.element.buttons.min_plus.mmap_button-plus_hover", mmap_plus_hover: "voxygen.element.buttons.min_plus.mmap_button-plus_hover",
mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_hover", mmap_plus_press: "voxygen.element.buttons.min_plus.mmap_button-plus_press",
mmap_minus: "voxygen.element.buttons.min_plus.mmap_button-min", mmap_minus: "voxygen.element.buttons.min_plus.mmap_button-min",
mmap_minus_hover: "voxygen.element.buttons.min_plus.mmap_button-min_hover", mmap_minus_hover: "voxygen.element.buttons.min_plus.mmap_button-min_hover",
mmap_minus_press: "voxygen.element.buttons.min_plus.mmap_button-min_press", mmap_minus_press: "voxygen.element.buttons.min_plus.mmap_button-min_press",

View File

@ -1,9 +1,12 @@
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR}; use super::{
img_ids::{Imgs, ImgsRot},
Fonts, Show, TEXT_COLOR,
};
use crate::ui::img_ids;
use client::{self, Client}; use client::{self, Client};
use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{ use conrod_core::{
color, color,
image::Id,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
@ -30,12 +33,13 @@ widget_ids! {
pub struct Map<'a> { pub struct Map<'a> {
_show: &'a Show, _show: &'a Show,
client: &'a Client, client: &'a Client,
world_map: (Id, Vec2<u32>), world_map: &'a (img_ids::Rotations, Vec2<u32>),
imgs: &'a Imgs, imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a Fonts, fonts: &'a Fonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
pulse: f32, _pulse: f32,
velocity: f32, velocity: f32,
} }
impl<'a> Map<'a> { impl<'a> Map<'a> {
@ -43,7 +47,8 @@ impl<'a> Map<'a> {
show: &'a Show, show: &'a Show,
client: &'a Client, client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
world_map: (Id, Vec2<u32>), rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts, fonts: &'a Fonts,
pulse: f32, pulse: f32,
velocity: f32, velocity: f32,
@ -51,11 +56,12 @@ impl<'a> Map<'a> {
Self { Self {
_show: show, _show: show,
imgs, imgs,
rot_imgs,
world_map, world_map,
client, client,
fonts, fonts,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
pulse, _pulse: pulse,
velocity, velocity,
} }
} }
@ -160,9 +166,9 @@ impl<'a> Widget for Map<'a> {
let (world_map, worldsize) = self.world_map; let (world_map, worldsize) = self.world_map;
let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64); let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64);
Image::new(world_map) Image::new(world_map.none)
.middle_of(state.ids.map_bg) .middle_of(state.ids.map_bg)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade - 0.1))) .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade + 0.5)))
.w_h(700.0, 700.0) .w_h(700.0, 700.0)
.parent(state.ids.map_bg) .parent(state.ids.map_bg)
.set(state.ids.grid, ui); .set(state.ids.grid, ui);
@ -177,28 +183,15 @@ impl<'a> Widget for Map<'a> {
let x = player_pos.x as f64 / worldsize.x * 700.0; let x = player_pos.x as f64 / worldsize.x * 700.0;
let y = player_pos.y as f64 / worldsize.y * 700.0; let y = player_pos.y as f64 / worldsize.y * 700.0;
let indic_ani = (self.pulse * 6.0/*animation speed*/).cos()/*starts at 1.0*/ * 0.5 + 0.50; // changes the animation frame let indic_scale = 0.6;
let indic_scale = 1.2; Image::new(self.rot_imgs.indicator_mmap_small.target_north)
// Indicator .bottom_left_with_margins_on(
Image::new(if indic_ani <= 0.3 { state.ids.grid,
self.imgs.indicator_mmap y - 37.0 * indic_scale / 2.0,
} else if indic_ani <= 0.6 { x - 32.0 * indic_scale / 2.0,
self.imgs.indicator_mmap_2
} else {
self.imgs.indicator_mmap_3
})
.bottom_left_with_margins_on(state.ids.grid, y, x - (20.0 * 1.2) / 2.0)
.w_h(
22.0 * 1.2,
if indic_ani <= 0.3 {
16.0 * indic_scale
} else if indic_ani <= 0.6 {
23.0 * indic_scale
} else {
34.0 * indic_scale
},
) )
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade + 0.2))) .w_h(32.0 * indic_scale, 37.0 * indic_scale)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.floating(true) .floating(true)
.parent(ui.window) .parent(ui.window)
.set(state.ids.indicator, ui); .set(state.ids.indicator, ui);

View File

@ -1,9 +1,12 @@
use super::{img_ids::Imgs, Fonts, Show, HP_COLOR, TEXT_COLOR}; use super::{
img_ids::{Imgs, ImgsRot},
Fonts, Show, HP_COLOR, TEXT_COLOR,
};
use crate::ui::img_ids;
use client::{self, Client}; use client::{self, Client};
use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{ use conrod_core::{
color, color, position,
image::Id,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
@ -33,12 +36,11 @@ pub struct MiniMap<'a> {
client: &'a Client, client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
world_map: (Id, Vec2<u32>), rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts, fonts: &'a Fonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
pulse: f32,
zoom: f32,
} }
impl<'a> MiniMap<'a> { impl<'a> MiniMap<'a> {
@ -46,20 +48,18 @@ impl<'a> MiniMap<'a> {
show: &'a Show, show: &'a Show,
client: &'a Client, client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
world_map: (Id, Vec2<u32>), rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts, fonts: &'a Fonts,
pulse: f32,
zoom: f32,
) -> Self { ) -> Self {
Self { Self {
show, show,
client, client,
imgs, imgs,
rot_imgs,
world_map, world_map,
fonts, fonts,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
pulse,
zoom,
} }
} }
} }
@ -69,6 +69,7 @@ pub struct State {
last_region_name: Option<String>, last_region_name: Option<String>,
last_update: Instant, last_update: Instant,
zoom: f64,
} }
pub enum Event { pub enum Event {
@ -86,6 +87,14 @@ impl<'a> Widget for MiniMap<'a> {
last_region_name: None, last_region_name: None,
last_update: Instant::now(), last_update: Instant::now(),
zoom: {
let min_world_dim = self.world_map.1.reduce_partial_min() as f64;
min_world_dim.min(
min_world_dim
* (TerrainChunkSize::RECT_SIZE.reduce_partial_max() as f64 / 32.0)
* (16.0 / 1024.0),
)
},
} }
} }
@ -93,53 +102,74 @@ impl<'a> Widget for MiniMap<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let zoom = self.zoom as f64; let zoom = state.zoom;
if self.show.mini_map { if self.show.mini_map {
Image::new(self.imgs.mmap_frame) Image::new(self.imgs.mmap_frame)
.w_h(100.0 * 4.0 * zoom, 100.0 * 4.0 * zoom) .w_h(100.0 * 3.0, 100.0 * 3.0)
.top_right_with_margins_on(ui.window, 5.0, 5.0) .top_right_with_margins_on(ui.window, 5.0, 5.0)
.set(state.ids.mmap_frame, ui); .set(state.ids.mmap_frame, ui);
Rectangle::fill_with([92.0 * 4.0, 82.0 * 4.0], color::TRANSPARENT) Rectangle::fill_with([92.0 * 3.0, 82.0 * 3.0], color::TRANSPARENT)
.mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 4.0 + 4.0 * zoom) .mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 3.0 + 3.0)
.set(state.ids.mmap_frame_bg, ui); .set(state.ids.mmap_frame_bg, ui);
// Zoom Buttons
// TODO: Add zoomable minimap
/*if Button::image(self.imgs.mmap_plus) // Map size
.w_h(100.0 * 0.2 * zoom, 100.0 * 0.2 * zoom)
.hover_image(self.imgs.mmap_plus_hover)
.press_image(self.imgs.mmap_plus_press)
.top_left_with_margins_on(state.ids.mmap_frame, 0.0, 0.0)
.set(state.ids.mmap_plus, ui)
.was_clicked()
{
if zoom > 0.0 {
zoom = zoom + 1.0
} else if zoom == 5.0 {
}
}
if Button::image(self.imgs.mmap_minus)
.w_h(100.0 * 0.2 * zoom, 100.0 * 0.2 * zoom)
.hover_image(self.imgs.mmap_minus_hover)
.press_image(self.imgs.mmap_minus_press)
.down_from(state.ids.mmap_plus, 0.0)
.set(state.ids.mmap_minus, ui)
.was_clicked()
{
if zoom < 6.0 {
zoom = zoom - 1.0
} else if zoom == 0.0 {
}
}*/
// Map Image
let (world_map, worldsize) = self.world_map; let (world_map, worldsize) = self.world_map;
let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64); let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64);
Image::new(world_map)
.middle_of(state.ids.mmap_frame_bg) // Zoom Buttons
.w_h(92.0 * 4.0 * zoom, 82.0 * 4.0 * zoom)
.parent(state.ids.mmap_frame_bg) // Pressing + multiplies, and - divides, zoom by ZOOM_FACTOR.
.set(state.ids.grid, ui); const ZOOM_FACTOR: f64 = 2.0;
// TODO: Either prevent zooming all the way in, *or* see if we can interpolate
// somehow if you zoom in too far. Or both.
let min_zoom = 1.0;
let max_zoom = (worldsize / TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
.reduce_partial_min()/*.min(f64::MAX)*/;
// NOTE: Not sure if a button can be clicked while disabled, but we still double
// check for both kinds of zoom to make sure that not only was the
// button clicked, it is also okay to perform the zoom action.
// Note that since `Button::image` has side effects, we must perform
// the `can_zoom_in` and `can_zoom_out` checks after the `&&` to avoid
// undesired early termination.
let can_zoom_in = zoom < max_zoom;
let can_zoom_out = zoom > min_zoom;
if Button::image(self.imgs.mmap_minus)
.w_h(100.0 * 0.30, 100.0 * 0.30)
.hover_image(self.imgs.mmap_minus_hover)
.press_image(self.imgs.mmap_minus_press)
.top_left_with_margins_on(state.ids.mmap_frame, 0.0, 0.0)
.enabled(can_zoom_out)
.set(state.ids.mmap_minus, ui)
.was_clicked()
&& can_zoom_out
{
// Set the image dimensions here, rather than recomputing each time.
let zoom = min_zoom.max(zoom / ZOOM_FACTOR);
state.update(|s| s.zoom = zoom);
// set_image_dims(zoom);
}
if Button::image(self.imgs.mmap_plus)
.w_h(100.0 * 0.30, 100.0 * 0.30)
.hover_image(self.imgs.mmap_plus_hover)
.press_image(self.imgs.mmap_plus_press)
.right_from(state.ids.mmap_minus, 6.0)
.enabled(can_zoom_in)
.set(state.ids.mmap_plus, ui)
.was_clicked()
&& can_zoom_in
{
let zoom = max_zoom.min(zoom * ZOOM_FACTOR);
state.update(|s| s.zoom = zoom);
// set_image_dims(zoom);
}
// Reload zoom in case it changed.
let zoom = state.zoom;
// Coordinates // Coordinates
let player_pos = self let player_pos = self
.client .client
@ -149,26 +179,32 @@ impl<'a> Widget for MiniMap<'a> {
.get(self.client.entity()) .get(self.client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);
let x = player_pos.x as f64 / worldsize.x * 92.0 * 4.0; // Get map image source rectangle dimensons.
let y = player_pos.y as f64 / worldsize.y * 82.0 * 4.0; let w_src = worldsize.x / TerrainChunkSize::RECT_SIZE.x as f64 / zoom;
let indic_ani = (self.pulse * 6.0).cos() * 0.5 + 0.5; //Animation timer let h_src = worldsize.y / TerrainChunkSize::RECT_SIZE.y as f64 / zoom;
let indic_scale = 0.8;
// Set map image to be centered around player coordinates.
let rect_src = position::Rect::from_xy_dim(
[
player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64,
(worldsize.y - player_pos.y as f64) / TerrainChunkSize::RECT_SIZE.y as f64,
],
[w_src, h_src],
);
// Map Image
Image::new(world_map.source_north)
.middle_of(state.ids.mmap_frame_bg)
.w_h(92.0 * 3.0, 82.0 * 3.0)
.parent(state.ids.mmap_frame_bg)
.source_rectangle(rect_src)
.set(state.ids.grid, ui);
// Indicator // Indicator
Image::new(if indic_ani <= 0.5 { let ind_scale = 0.4;
self.imgs.indicator_mmap Image::new(self.rot_imgs.indicator_mmap_small.none)
} else { .middle_of(state.ids.grid)
self.imgs.indicator_mmap_2 .w_h(32.0 * ind_scale, 37.0 * ind_scale)
})
.bottom_left_with_margins_on(state.ids.grid, y, x - 5.0)
.w_h(
// Animation frames depening on timer value from 0.0 to 1.0
22.0 * 0.8,
if indic_ani <= 0.5 {
18.0 * indic_scale
} else {
23.0 * indic_scale
},
)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.floating(true) .floating(true)
.parent(ui.window) .parent(ui.window)
@ -186,9 +222,9 @@ impl<'a> Widget for MiniMap<'a> {
self.imgs.mmap_closed self.imgs.mmap_closed
}) })
.wh(if self.show.mini_map { .wh(if self.show.mini_map {
[100.0 * 0.4; 2] [100.0 * 0.3; 2]
} else { } else {
[100.0 * 0.2 * zoom; 2] [100.0 * 0.2; 2]
}) })
.hover_image(if self.show.mini_map { .hover_image(if self.show.mini_map {
self.imgs.mmap_open_hover self.imgs.mmap_open_hover
@ -267,7 +303,7 @@ impl<'a> Widget for MiniMap<'a> {
state.ids.mmap_frame, state.ids.mmap_frame,
if self.show.mini_map { 6.0 } else { 0.0 }, if self.show.mini_map { 6.0 } else { 0.0 },
) )
.font_size(if self.show.mini_map { 30 } else { 18 }) .font_size(if self.show.mini_map { 20 } else { 18 })
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.mmap_location, ui), .set(state.ids.mmap_location, ui),

View File

@ -13,7 +13,7 @@ mod skillbar;
mod social; mod social;
mod spell; mod spell;
use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot}; use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot, ui::img_ids::Rotations};
pub use settings_window::ScaleChange; pub use settings_window::ScaleChange;
use std::time::Duration; use std::time::Duration;
@ -46,7 +46,6 @@ use crate::{
use client::{Client, Event as ClientEvent}; use client::{Client, Event as ClientEvent};
use common::{assets::load_expect, comp, terrain::TerrainChunk, vol::RectRasterableVol}; use common::{assets::load_expect, comp, terrain::TerrainChunk, vol::RectRasterableVol};
use conrod_core::{ use conrod_core::{
image::Id,
text::cursor::Index, text::cursor::Index,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
@ -307,7 +306,7 @@ impl Show {
fn map(&mut self, open: bool) { fn map(&mut self, open: bool) {
self.map = open; self.map = open;
self.bag = false; self.bag = false;
self.want_grab = !open; self.want_grab = true;
} }
fn character_window(&mut self, open: bool) { fn character_window(&mut self, open: bool) {
@ -431,7 +430,7 @@ impl Show {
pub struct Hud { pub struct Hud {
ui: Ui, ui: Ui,
ids: Ids, ids: Ids,
world_map: (Id, Vec2<u32>), world_map: (/* Id */ Rotations, Vec2<u32>),
imgs: Imgs, imgs: Imgs,
item_imgs: ItemImgs, item_imgs: ItemImgs,
fonts: Fonts, fonts: Fonts,
@ -446,7 +445,6 @@ pub struct Hud {
force_chat_input: Option<String>, force_chat_input: Option<String>,
force_chat_cursor: Option<Index>, force_chat_cursor: Option<Index>,
pulse: f32, pulse: f32,
zoom: f32,
velocity: f32, velocity: f32,
} }
@ -461,7 +459,7 @@ impl Hud {
let ids = Ids::new(ui.id_generator()); let ids = Ids::new(ui.id_generator());
// Load world map // Load world map
let world_map = ( let world_map = (
ui.add_graphic(Graphic::Image(client.world_map.0.clone())), ui.add_graphic_with_rotations(Graphic::Image(client.world_map.0.clone())),
client.world_map.1, client.world_map.1,
); );
// Load images. // Load images.
@ -497,7 +495,7 @@ impl Hud {
quest: false, quest: false,
spell: false, spell: false,
character_window: false, character_window: false,
mini_map: false, mini_map: true,
settings_tab: SettingsTab::Interface, settings_tab: SettingsTab::Interface,
social_tab: SocialTab::Online, social_tab: SocialTab::Online,
want_grab: true, want_grab: true,
@ -509,7 +507,6 @@ impl Hud {
force_chat_input: None, force_chat_input: None,
force_chat_cursor: None, force_chat_cursor: None,
pulse: 0.0, pulse: 0.0,
zoom: 1.0,
velocity: 0.0, velocity: 0.0,
} }
} }
@ -1590,10 +1587,9 @@ impl Hud {
&self.show, &self.show,
client, client,
&self.imgs, &self.imgs,
self.world_map, &self.rot_imgs,
&self.world_map,
&self.fonts, &self.fonts,
self.pulse,
self.zoom,
) )
.set(self.ids.minimap, ui_widgets) .set(self.ids.minimap, ui_widgets)
{ {
@ -1864,7 +1860,8 @@ impl Hud {
&self.show, &self.show,
client, client,
&self.imgs, &self.imgs,
self.world_map, &self.rot_imgs,
&self.world_map,
&self.fonts, &self.fonts,
self.pulse, self.pulse,
self.velocity, self.velocity,

View File

@ -10,6 +10,7 @@ gfx_defines! {
pos: [f32; 2] = "v_pos", pos: [f32; 2] = "v_pos",
uv: [f32; 2] = "v_uv", uv: [f32; 2] = "v_uv",
color: [f32; 4] = "v_color", color: [f32; 4] = "v_color",
center: [f32; 2] = "v_center",
mode: u32 = "v_mode", mode: u32 = "v_mode",
} }
@ -55,11 +56,23 @@ pub const MODE_TEXT: u32 = 0;
pub const MODE_IMAGE: u32 = 1; pub const MODE_IMAGE: u32 = 1;
/// Ignore `tex` and draw simple, colored 2D geometry. /// Ignore `tex` and draw simple, colored 2D geometry.
pub const MODE_GEOMETRY: u32 = 2; pub const MODE_GEOMETRY: u32 = 2;
/// Draw an image from the texture at `tex` in the fragment shader, with the
/// source rectangle rotated to face north.
///
/// FIXME: Make more principled.
pub const MODE_IMAGE_SOURCE_NORTH: u32 = 3;
/// Draw an image from the texture at `tex` in the fragment shader, with the
/// target rectangle rotated to face north.
///
/// FIXME: Make more principled.
pub const MODE_IMAGE_TARGET_NORTH: u32 = 5;
pub enum Mode { pub enum Mode {
Text, Text,
Image, Image,
Geometry, Geometry,
ImageSourceNorth,
ImageTargetNorth,
} }
impl Mode { impl Mode {
@ -68,6 +81,8 @@ impl Mode {
Mode::Text => MODE_TEXT, Mode::Text => MODE_TEXT,
Mode::Image => MODE_IMAGE, Mode::Image => MODE_IMAGE,
Mode::Geometry => MODE_GEOMETRY, Mode::Geometry => MODE_GEOMETRY,
Mode::ImageSourceNorth => MODE_IMAGE_SOURCE_NORTH,
Mode::ImageTargetNorth => MODE_IMAGE_TARGET_NORTH,
} }
} }
} }
@ -78,10 +93,16 @@ pub fn create_quad(
color: Rgba<f32>, color: Rgba<f32>,
mode: Mode, mode: Mode,
) -> Quad<UiPipeline> { ) -> Quad<UiPipeline> {
let center = if let Mode::ImageSourceNorth = mode {
uv_rect.center().into_array()
} else {
rect.center().into_array()
};
let mode_val = mode.value(); let mode_val = mode.value();
let v = |pos, uv| Vertex { let v = |pos, uv| Vertex {
pos, pos,
uv, uv,
center,
color: color.into_array(), color: color.into_array(),
mode: mode_val, mode: mode_val,
}; };
@ -118,10 +139,12 @@ pub fn create_tri(
color: Rgba<f32>, color: Rgba<f32>,
mode: Mode, mode: Mode,
) -> Tri<UiPipeline> { ) -> Tri<UiPipeline> {
let center = [0.0, 0.0];
let mode_val = mode.value(); let mode_val = mode.value();
let v = |pos, uv| Vertex { let v = |pos, uv| Vertex {
pos, pos,
uv, uv,
center,
color: color.into_array(), color: color.into_array(),
mode: mode_val, mode: mode_val,
}; };

View File

@ -190,7 +190,7 @@ impl Camera {
} }
/// Get the focus position of the camera. /// Get the focus position of the camera.
pub fn get_focus_pos(&self) -> Vec3<f32> { self.tgt_focus } pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
/// Set the focus position of the camera. /// Set the focus position of the camera.
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; } pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; }

View File

@ -6,7 +6,7 @@ pub use renderer::{SampleStrat, Transform};
use crate::render::{Renderer, Texture}; use crate::render::{Renderer, Texture};
use dot_vox::DotVoxData; use dot_vox::DotVoxData;
use guillotiere::{size2, SimpleAtlasAllocator}; use guillotiere::{size2, SimpleAtlasAllocator};
use hashbrown::HashMap; use hashbrown::{hash_map::Entry, HashMap};
use image::{DynamicImage, RgbaImage}; use image::{DynamicImage, RgbaImage};
use log::warn; use log::warn;
use pixel_art::resize_pixel_art; use pixel_art::resize_pixel_art;
@ -26,6 +26,14 @@ pub enum Rotation {
Cw90, Cw90,
Cw180, Cw180,
Cw270, Cw270,
/// Orientation of source rectangle that always faces true north.
/// Simple hack to get around Conrod not having space for proper
/// rotation data (though it should be possible to add in other ways).
SourceNorth,
/// Orientation of target rectangle that always faces true north.
/// Simple hack to get around Conrod not having space for proper
/// rotation data (though it should be possible to add in other ways).
TargetNorth,
} }
/// Images larger than this are stored in individual textures /// Images larger than this are stored in individual textures
@ -41,7 +49,7 @@ pub struct Id(u32);
#[derive(PartialEq, Eq, Hash, Copy, Clone)] #[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct TexId(usize); pub struct TexId(usize);
type Parameters = (Id, Vec2<u16>, Aabr<u64>); type Parameters = (Id, Vec2<u16>);
type GraphicMap = HashMap<Id, Graphic>; type GraphicMap = HashMap<Id, Graphic>;
enum CacheLoc { enum CacheLoc {
@ -130,6 +138,8 @@ impl GraphicCache {
self.textures = vec![texture]; self.textures = vec![texture];
} }
/// Source rectangle should be from 0 to 1, and represents a bounding box
/// for the source image of the graphic.
pub fn cache_res( pub fn cache_res(
&mut self, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
@ -137,15 +147,18 @@ impl GraphicCache {
dims: Vec2<u16>, dims: Vec2<u16>,
source: Aabr<f64>, source: Aabr<f64>,
rotation: Rotation, rotation: Rotation,
) -> Option<(Aabr<u16>, TexId)> { ) -> Option<(Aabr<f64>, TexId)> {
let dims = match rotation { let dims = match rotation {
Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x), Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x),
Rotation::None | Rotation::Cw180 => dims, Rotation::None | Rotation::Cw180 => dims,
Rotation::SourceNorth => dims,
Rotation::TargetNorth => dims,
}; };
let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source let key = (graphic_id, dims);
// Rotate aabr according to requested rotation.
let rotated_aabr = |Aabr { min, max }| match rotation { let rotated_aabr = |Aabr { min, max }| match rotation {
Rotation::None => Aabr { min, max }, Rotation::None | Rotation::SourceNorth | Rotation::TargetNorth => Aabr { min, max },
Rotation::Cw90 => Aabr { Rotation::Cw90 => Aabr {
min: Vec2::new(min.x, max.y), min: Vec2::new(min.x, max.y),
max: Vec2::new(max.x, min.y), max: Vec2::new(max.x, min.y),
@ -156,8 +169,21 @@ impl GraphicCache {
max: Vec2::new(min.x, max.y), max: Vec2::new(min.x, max.y),
}, },
}; };
// Scale aabr according to provided source rectangle.
let scaled_aabr = |aabr: Aabr<_>| {
let size: Vec2<_> = aabr.size().into();
Aabr {
min: size.mul_add(source.min, aabr.min),
max: size.mul_add(source.max, aabr.min),
}
};
// Apply all transformations.
// TODO: Verify rotation is being applied correctly.
let transformed_aabr = |aabr| rotated_aabr(scaled_aabr(aabr));
if let Some(details) = self.cache_map.get(&key) { let details = match self.cache_map.entry(key) {
Entry::Occupied(details) => {
let details = details.get();
let (idx, aabr) = match details.location { let (idx, aabr) = match details.location {
CacheLoc::Atlas { CacheLoc::Atlas {
atlas_idx, aabr, .. atlas_idx, aabr, ..
@ -180,8 +206,11 @@ impl GraphicCache {
upload_image(renderer, aabr, &self.textures[idx], &image); upload_image(renderer, aabr, &self.textures[idx], &image);
} }
Some((rotated_aabr(aabr), TexId(idx))) return Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx)));
} else { },
Entry::Vacant(details) => details,
};
// Create image // Create image
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
@ -196,8 +225,7 @@ impl GraphicCache {
// Fit into an atlas // Fit into an atlas
let mut loc = None; let mut loc = None;
for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() { for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() {
if let Some(rectangle) = if let Some(rectangle) = atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
{ {
let aabr = aabr_from_alloc_rect(rectangle); let aabr = aabr_from_alloc_rect(rectangle);
loc = Some(CacheLoc::Atlas { atlas_idx, aabr }); loc = Some(CacheLoc::Atlas { atlas_idx, aabr });
@ -244,13 +272,12 @@ impl GraphicCache {
// Upload // Upload
upload_image(renderer, aabr, &self.textures[idx], &image); upload_image(renderer, aabr, &self.textures[idx], &image);
// Insert into cached map // Insert into cached map
self.cache_map.insert(key, CachedDetails { details.insert(CachedDetails {
location, location,
valid: true, valid: true,
}); });
Some((rotated_aabr(aabr), TexId(idx))) Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx)))
}
} }
} }

View File

@ -106,6 +106,8 @@ pub struct Rotations {
pub cw90: conrod_core::image::Id, pub cw90: conrod_core::image::Id,
pub cw180: conrod_core::image::Id, pub cw180: conrod_core::image::Id,
pub cw270: conrod_core::image::Id, pub cw270: conrod_core::image::Id,
pub source_north: conrod_core::image::Id,
pub target_north: conrod_core::image::Id,
} }
/// This macro will automatically load all specified assets, get the /// This macro will automatically load all specified assets, get the

View File

@ -28,6 +28,7 @@ use crate::{
window::Window, window::Window,
Error, Error,
}; };
use ::image::GenericImageView;
use cache::Cache; use cache::Cache;
use common::{assets, util::srgba_to_linear}; use common::{assets, util::srgba_to_linear};
use conrod_core::{ use conrod_core::{
@ -43,6 +44,7 @@ use conrod_core::{
use graphic::{Rotation, TexId}; use graphic::{Rotation, TexId};
use log::{error, warn}; use log::{error, warn};
use std::{ use std::{
f32, f64,
fs::File, fs::File,
io::{BufReader, Read}, io::{BufReader, Read},
ops::Range, ops::Range,
@ -172,6 +174,16 @@ impl Ui {
cw90: self.image_map.insert((graphic_id, Rotation::Cw90)), cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
cw180: self.image_map.insert((graphic_id, Rotation::Cw180)), cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
cw270: self.image_map.insert((graphic_id, Rotation::Cw270)), cw270: self.image_map.insert((graphic_id, Rotation::Cw270)),
// Hacky way to make sure a source rectangle always faces north regardless of player
// orientation.
// This is an easy way to get around Conrod's lack of rotation data for images (for this
// specific use case).
source_north: self.image_map.insert((graphic_id, Rotation::SourceNorth)),
// Hacky way to make sure a target rectangle always faces north regardless of player
// orientation.
// This is an easy way to get around Conrod's lack of rotation data for images (for this
// specific use case).
target_north: self.image_map.insert((graphic_id, Rotation::TargetNorth)),
} }
} }
@ -420,46 +432,88 @@ impl Ui {
PrimitiveKind::Image { PrimitiveKind::Image {
image_id, image_id,
color, color,
source_rect: _, // TODO: <-- use this source_rect,
} => { } => {
let (graphic_id, rotation) = self let (graphic_id, rotation) = self
.image_map .image_map
.get(&image_id) .get(&image_id)
.expect("Image does not exist in image map"); .expect("Image does not exist in image map");
let graphic_cache = self.cache.graphic_cache_mut(); let graphic_cache = self.cache.graphic_cache_mut();
let gl_aabr = gl_aabr(rect);
let (source_aabr, gl_size) = {
// Transform the source rectangle into uv coordinate.
// TODO: Make sure this is right. Especially the conversions.
let ((uv_l, uv_r, uv_b, uv_t), gl_size) =
match graphic_cache.get_graphic(*graphic_id) { match graphic_cache.get_graphic(*graphic_id) {
Some(Graphic::Blank) | None => continue, Some(Graphic::Blank) | None => continue,
_ => {}, Some(Graphic::Image(image)) => {
source_rect.and_then(|src_rect| {
let (image_w, image_h) = image.dimensions();
let (source_w, source_h) = src_rect.w_h();
let gl_size = gl_aabr.size();
if image_w == 0
|| image_h == 0
|| source_w < 1.0
|| source_h < 1.0
|| gl_size.reduce_partial_max() < f32::EPSILON
{
None
} else {
// Multiply drawn image size by ratio of original image
// size to
// source rectangle size (since as the proportion of the
// image gets
// smaller, the drawn size should get bigger), up to the
// actual
// size of the original image.
let ratio_x = (image_w as f64 / source_w).min(
(image_w as f64 / (gl_size.w * half_res.x) as f64)
.max(1.0),
);
let ratio_y = (image_h as f64 / source_h).min(
(image_h as f64 / (gl_size.h * half_res.y) as f64)
.max(1.0),
);
let (l, r, b, t) = src_rect.l_r_b_t();
Some((
(
l / image_w as f64, /* * ratio_x*/
r / image_w as f64, /* * ratio_x*/
b / image_h as f64, /* * ratio_y*/
t / image_h as f64, /* * ratio_y*/
),
Extent2::new(
(gl_size.w as f64 * ratio_x) as f32,
(gl_size.h as f64 * ratio_y) as f32,
),
))
/* ((l / image_w as f64),
(r / image_w as f64),
(b / image_h as f64),
(t / image_h as f64)) */
} }
})
},
// No easy way to interpret source_rect for voxels...
Some(Graphic::Voxel(..)) => None,
}
.unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size()));
(
Aabr {
min: Vec2::new(uv_l, uv_b),
max: Vec2::new(uv_r, uv_t),
},
gl_size,
)
};
let color = let color =
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into()); srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
let gl_aabr = gl_aabr(rect);
let resolution = Vec2::new( let resolution = Vec2::new(
(gl_aabr.size().w * half_res.x).round() as u16, (gl_size.w * half_res.x).round() as u16,
(gl_aabr.size().h * half_res.y).round() as u16, (gl_size.h * half_res.y).round() as u16,
); );
// Transform the source rectangle into uv coordinate.
// TODO: Make sure this is right.
let source_aabr = {
let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0);
/*match source_rect {
Some(src_rect) => {
let (l, r, b, t) = src_rect.l_r_b_t();
((l / image_w) as f32,
(r / image_w) as f32,
(b / image_h) as f32,
(t / image_h) as f32)
}
None => (0.0, 1.0, 0.0, 1.0),
};*/
Aabr {
min: Vec2::new(uv_l, uv_b),
max: Vec2::new(uv_r, uv_t),
}
};
// Cache graphic at particular resolution. // Cache graphic at particular resolution.
let (uv_aabr, tex_id) = match graphic_cache.cache_res( let (uv_aabr, tex_id) = match graphic_cache.cache_res(
@ -500,7 +554,13 @@ impl Ui {
State::Image(_) => {}, State::Image(_) => {},
} }
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image)); mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation {
Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => {
UiMode::Image
},
Rotation::SourceNorth => UiMode::ImageSourceNorth,
Rotation::TargetNorth => UiMode::ImageTargetNorth,
}));
}, },
PrimitiveKind::Text { PrimitiveKind::Text {
color, color,

View File

@ -239,6 +239,9 @@ impl MapConfig {
} }
} }
let water_color_factor = 2.0;
let g_water = 32.0 * water_color_factor;
let b_water = 64.0 * water_color_factor;
let rgba = match (river_kind, (is_water, true_alt >= true_sea_level)) { let rgba = match (river_kind, (is_water, true_alt >= true_sea_level)) {
(_, (false, _)) | (None, (_, true)) => { (_, (false, _)) | (None, (_, true)) => {
let (r, g, b) = ( let (r, g, b) = (
@ -251,7 +254,7 @@ impl MapConfig {
0.0 0.0
}) })
.sqrt(), .sqrt(),
if is_shaded { 0.2 + (alt * 0.8) } else { alt }, if is_shaded { 0.4 + (alt * 0.6) } else { alt },
(if is_shaded { alt } else { alt } (if is_shaded { alt } else { alt }
* if is_humidity { * if is_humidity {
humidity as f64 humidity as f64
@ -272,17 +275,22 @@ impl MapConfig {
}, },
(Some(RiverKind::Ocean), _) => ( (Some(RiverKind::Ocean), _) => (
0, 0,
((32.0 - water_depth * 32.0) * 1.0) as u8, ((g_water - water_depth * g_water) * 1.0) as u8,
((64.0 - water_depth * 64.0) * 1.0) as u8, ((b_water - water_depth * b_water) * 1.0) as u8,
255,
),
(Some(RiverKind::River { .. }), _) => (
0,
g_water as u8 + (alt * (127.0 - g_water)) as u8,
b_water as u8 + (alt * (255.0 - b_water)) as u8,
255, 255,
), ),
(Some(RiverKind::River { .. }), _) => {
(0, 32 + (alt * 95.0) as u8, 64 + (alt * 191.0) as u8, 255)
},
(None, _) | (Some(RiverKind::Lake { .. }), _) => ( (None, _) | (Some(RiverKind::Lake { .. }), _) => (
0, 0,
(((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8, (((g_water + water_alt * (127.0 - 32.0)) + (-water_depth * g_water)) * 1.0)
(((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8, as u8,
(((b_water + water_alt * (255.0 - b_water)) + (-water_depth * b_water))
* 1.0) as u8,
255, 255,
), ),
}; };

View File

@ -292,6 +292,9 @@ impl WorldFile {
pub struct WorldSim { pub struct WorldSim {
pub seed: u32, pub seed: u32,
/// Maximum height above sea level of any chunk in the map (not including
/// post-erosion warping, cliffs, and other things like that).
pub max_height: f32,
pub(crate) chunks: Vec<SimChunk>, pub(crate) chunks: Vec<SimChunk>,
pub(crate) locations: Vec<Location>, pub(crate) locations: Vec<Location>,
@ -1288,6 +1291,7 @@ impl WorldSim {
let mut this = Self { let mut this = Self {
seed, seed,
max_height: maxh as f32,
chunks, chunks,
locations: Vec::new(), locations: Vec::new(),
gen_ctx, gen_ctx,
@ -1306,7 +1310,11 @@ impl WorldSim {
pub fn get_map(&self) -> Vec<u32> { pub fn get_map(&self) -> Vec<u32> {
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y]; let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
// TODO: Parallelize again. // TODO: Parallelize again.
MapConfig::default().generate(&self, |pos, (r, g, b, a)| { MapConfig {
gain: self.max_height,
..MapConfig::default()
}
.generate(&self, |pos, (r, g, b, a)| {
v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]); v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]);
}); });
v v