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 music system
- Added zoomable and rotatable minimap
- Added rotating orientation marker to main-map
### Changed
- Brighter / higher contrast main-map
### 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)) {
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
// 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);
// 2D Geometry
} else if (f_mode == uint(2)) {

View File

@ -4,6 +4,7 @@
in vec2 v_pos;
in vec2 v_uv;
in vec2 v_center;
in vec4 v_color;
in uint v_mode;
@ -19,15 +20,31 @@ flat out uint f_mode;
out vec4 f_color;
void main() {
f_uv = v_uv;
f_color = v_color;
if (w_pos.w == 1.0) {
f_uv = v_uv;
// Fixed scale In-game element
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);
} 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 {
// Interface element
f_uv = v_uv;
gl_Position = vec4(v_pos, 0.0, 1.0);
}
f_mode = v_mode;

View File

@ -8,6 +8,13 @@ rotation_image_ids! {
// Tooltip Test
tt_side: "voxygen/element/frames/tt_test_edge",
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",
// Map
map_indicator: "voxygen.element.buttons.map_indicator",
indicator_mmap: "voxygen.element.buttons.indicator_mmap",
indicator_mmap_2: "voxygen.element.buttons.indicator_mmap_2",
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_plus: "voxygen.element.buttons.min_plus.mmap_button-plus",
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_hover: "voxygen.element.buttons.min_plus.mmap_button-min_hover",
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 common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{
color,
image::Id,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
@ -30,12 +33,13 @@ widget_ids! {
pub struct Map<'a> {
_show: &'a Show,
client: &'a Client,
world_map: (Id, Vec2<u32>),
world_map: &'a (img_ids::Rotations, Vec2<u32>),
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
pulse: f32,
_pulse: f32,
velocity: f32,
}
impl<'a> Map<'a> {
@ -43,7 +47,8 @@ impl<'a> Map<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
world_map: (Id, Vec2<u32>),
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts,
pulse: f32,
velocity: f32,
@ -51,11 +56,12 @@ impl<'a> Map<'a> {
Self {
_show: show,
imgs,
rot_imgs,
world_map,
client,
fonts,
common: widget::CommonBuilder::default(),
pulse,
_pulse: pulse,
velocity,
}
}
@ -160,9 +166,9 @@ impl<'a> Widget for Map<'a> {
let (world_map, worldsize) = self.world_map;
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)
.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)
.parent(state.ids.map_bg)
.set(state.ids.grid, ui);
@ -177,31 +183,18 @@ impl<'a> Widget for Map<'a> {
let x = player_pos.x as f64 / worldsize.x * 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 = 1.2;
// Indicator
Image::new(if indic_ani <= 0.3 {
self.imgs.indicator_mmap
} else if indic_ani <= 0.6 {
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)))
.floating(true)
.parent(ui.window)
.set(state.ids.indicator, ui);
let indic_scale = 0.6;
Image::new(self.rot_imgs.indicator_mmap_small.target_north)
.bottom_left_with_margins_on(
state.ids.grid,
y - 37.0 * indic_scale / 2.0,
x - 32.0 * indic_scale / 2.0,
)
.w_h(32.0 * indic_scale, 37.0 * indic_scale)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.floating(true)
.parent(ui.window)
.set(state.ids.indicator, ui);
None
}

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 common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{
color,
image::Id,
color, position,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
@ -33,12 +36,11 @@ pub struct MiniMap<'a> {
client: &'a Client,
imgs: &'a Imgs,
world_map: (Id, Vec2<u32>),
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
pulse: f32,
zoom: f32,
}
impl<'a> MiniMap<'a> {
@ -46,20 +48,18 @@ impl<'a> MiniMap<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
world_map: (Id, Vec2<u32>),
rot_imgs: &'a ImgsRot,
world_map: &'a (img_ids::Rotations, Vec2<u32>),
fonts: &'a Fonts,
pulse: f32,
zoom: f32,
) -> Self {
Self {
show,
client,
imgs,
rot_imgs,
world_map,
fonts,
common: widget::CommonBuilder::default(),
pulse,
zoom,
}
}
}
@ -69,6 +69,7 @@ pub struct State {
last_region_name: Option<String>,
last_update: Instant,
zoom: f64,
}
pub enum Event {
@ -86,6 +87,14 @@ impl<'a> Widget for MiniMap<'a> {
last_region_name: None,
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 {
let widget::UpdateArgs { state, ui, .. } = args;
let zoom = self.zoom as f64;
let zoom = state.zoom;
if self.show.mini_map {
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)
.set(state.ids.mmap_frame, ui);
Rectangle::fill_with([92.0 * 4.0, 82.0 * 4.0], color::TRANSPARENT)
.mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 4.0 + 4.0 * zoom)
Rectangle::fill_with([92.0 * 3.0, 82.0 * 3.0], color::TRANSPARENT)
.mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 3.0 + 3.0)
.set(state.ids.mmap_frame_bg, ui);
// Zoom Buttons
// TODO: Add zoomable minimap
/*if Button::image(self.imgs.mmap_plus)
.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
// Map size
let (world_map, worldsize) = self.world_map;
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)
.w_h(92.0 * 4.0 * zoom, 82.0 * 4.0 * zoom)
.parent(state.ids.mmap_frame_bg)
.set(state.ids.grid, ui);
// Zoom Buttons
// Pressing + multiplies, and - divides, zoom by ZOOM_FACTOR.
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
let player_pos = self
.client
@ -149,30 +179,36 @@ impl<'a> Widget for MiniMap<'a> {
.get(self.client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
let x = player_pos.x as f64 / worldsize.x * 92.0 * 4.0;
let y = player_pos.y as f64 / worldsize.y * 82.0 * 4.0;
let indic_ani = (self.pulse * 6.0).cos() * 0.5 + 0.5; //Animation timer
let indic_scale = 0.8;
// Get map image source rectangle dimensons.
let w_src = worldsize.x / TerrainChunkSize::RECT_SIZE.x as f64 / zoom;
let h_src = worldsize.y / TerrainChunkSize::RECT_SIZE.y as f64 / zoom;
// 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
Image::new(if indic_ani <= 0.5 {
self.imgs.indicator_mmap
} else {
self.imgs.indicator_mmap_2
})
.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)))
.floating(true)
.parent(ui.window)
.set(state.ids.indicator, ui);
let ind_scale = 0.4;
Image::new(self.rot_imgs.indicator_mmap_small.none)
.middle_of(state.ids.grid)
.w_h(32.0 * ind_scale, 37.0 * ind_scale)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.floating(true)
.parent(ui.window)
.set(state.ids.indicator, ui);
} else {
Image::new(self.imgs.mmap_frame_closed)
.w_h(100.0 * 2.0, 11.0 * 2.0)
@ -186,9 +222,9 @@ impl<'a> Widget for MiniMap<'a> {
self.imgs.mmap_closed
})
.wh(if self.show.mini_map {
[100.0 * 0.4; 2]
[100.0 * 0.3; 2]
} else {
[100.0 * 0.2 * zoom; 2]
[100.0 * 0.2; 2]
})
.hover_image(if self.show.mini_map {
self.imgs.mmap_open_hover
@ -267,7 +303,7 @@ impl<'a> Widget for MiniMap<'a> {
state.ids.mmap_frame,
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)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui),

View File

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

View File

@ -10,6 +10,7 @@ gfx_defines! {
pos: [f32; 2] = "v_pos",
uv: [f32; 2] = "v_uv",
color: [f32; 4] = "v_color",
center: [f32; 2] = "v_center",
mode: u32 = "v_mode",
}
@ -55,11 +56,23 @@ pub const MODE_TEXT: u32 = 0;
pub const MODE_IMAGE: u32 = 1;
/// Ignore `tex` and draw simple, colored 2D geometry.
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 {
Text,
Image,
Geometry,
ImageSourceNorth,
ImageTargetNorth,
}
impl Mode {
@ -68,6 +81,8 @@ impl Mode {
Mode::Text => MODE_TEXT,
Mode::Image => MODE_IMAGE,
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>,
mode: Mode,
) -> 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 v = |pos, uv| Vertex {
pos,
uv,
center,
color: color.into_array(),
mode: mode_val,
};
@ -118,10 +139,12 @@ pub fn create_tri(
color: Rgba<f32>,
mode: Mode,
) -> Tri<UiPipeline> {
let center = [0.0, 0.0];
let mode_val = mode.value();
let v = |pos, uv| Vertex {
pos,
uv,
center,
color: color.into_array(),
mode: mode_val,
};

View File

@ -190,7 +190,7 @@ impl 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.
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 dot_vox::DotVoxData;
use guillotiere::{size2, SimpleAtlasAllocator};
use hashbrown::HashMap;
use hashbrown::{hash_map::Entry, HashMap};
use image::{DynamicImage, RgbaImage};
use log::warn;
use pixel_art::resize_pixel_art;
@ -26,6 +26,14 @@ pub enum Rotation {
Cw90,
Cw180,
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
@ -41,7 +49,7 @@ pub struct Id(u32);
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct TexId(usize);
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
type Parameters = (Id, Vec2<u16>);
type GraphicMap = HashMap<Id, Graphic>;
enum CacheLoc {
@ -130,6 +138,8 @@ impl GraphicCache {
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(
&mut self,
renderer: &mut Renderer,
@ -137,15 +147,18 @@ impl GraphicCache {
dims: Vec2<u16>,
source: Aabr<f64>,
rotation: Rotation,
) -> Option<(Aabr<u16>, TexId)> {
) -> Option<(Aabr<f64>, TexId)> {
let dims = match rotation {
Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x),
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 {
Rotation::None => Aabr { min, max },
Rotation::None | Rotation::SourceNorth | Rotation::TargetNorth => Aabr { min, max },
Rotation::Cw90 => Aabr {
min: Vec2::new(min.x, max.y),
max: Vec2::new(max.x, min.y),
@ -156,101 +169,115 @@ impl GraphicCache {
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 (idx, aabr) = match details.location {
CacheLoc::Atlas {
atlas_idx, aabr, ..
} => (self.atlases[atlas_idx].1, aabr),
CacheLoc::Texture { index } => {
(index, Aabr {
min: Vec2::new(0, 0),
// Note texture should always match the cached dimensions
max: dims,
})
},
};
let details = match self.cache_map.entry(key) {
Entry::Occupied(details) => {
let details = details.get();
let (idx, aabr) = match details.location {
CacheLoc::Atlas {
atlas_idx, aabr, ..
} => (self.atlases[atlas_idx].1, aabr),
CacheLoc::Texture { index } => {
(index, Aabr {
min: Vec2::new(0, 0),
// Note texture should always match the cached dimensions
max: dims,
})
},
};
// Check if the cached version has been invalidated by replacing the underlying
// graphic
if !details.valid {
// Create image
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
// Transfer to the gpu
upload_image(renderer, aabr, &self.textures[idx], &image);
// Check if the cached version has been invalidated by replacing the underlying
// graphic
if !details.valid {
// Create image
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
// Transfer to the gpu
upload_image(renderer, aabr, &self.textures[idx], &image);
}
return Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx)));
},
Entry::Vacant(details) => details,
};
// Create image
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
// Allocate space on the gpu
// Check size of graphic
// Graphics over a particular size are sent to their own textures
let location = if Vec2::<i32>::from(self.atlases[0].0.size().to_tuple())
.map(|e| e as u16)
.map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32)
.reduce_and()
{
// Fit into an atlas
let mut loc = None;
for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() {
if let Some(rectangle) = atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
{
let aabr = aabr_from_alloc_rect(rectangle);
loc = Some(CacheLoc::Atlas { atlas_idx, aabr });
break;
}
}
Some((rotated_aabr(aabr), TexId(idx)))
} else {
// Create image
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
// Allocate space on the gpu
// Check size of graphic
// Graphics over a particular size are sent to their own textures
let location = if Vec2::<i32>::from(self.atlases[0].0.size().to_tuple())
.map(|e| e as u16)
.map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32)
.reduce_and()
{
// Fit into an atlas
let mut loc = None;
for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() {
if let Some(rectangle) =
atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
{
let aabr = aabr_from_alloc_rect(rectangle);
loc = Some(CacheLoc::Atlas { atlas_idx, aabr });
break;
}
}
match loc {
Some(loc) => loc,
// Create a new atlas
None => {
let (mut atlas, texture) = create_atlas_texture(renderer);
let aabr = atlas
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
.map(aabr_from_alloc_rect)
.unwrap();
let tex_idx = self.textures.len();
let atlas_idx = self.atlases.len();
self.textures.push(texture);
self.atlases.push((atlas, tex_idx));
CacheLoc::Atlas { atlas_idx, aabr }
},
}
} else {
// Create a texture just for this
let texture = renderer.create_dynamic_texture(dims).unwrap();
let index = self.textures.len();
self.textures.push(texture);
CacheLoc::Texture { index }
};
let (idx, aabr) = match location {
CacheLoc::Atlas {
atlas_idx, aabr, ..
} => (self.atlases[atlas_idx].1, aabr),
CacheLoc::Texture { index } => {
(index, Aabr {
min: Vec2::new(0, 0),
// Note texture should always match the cached dimensions
max: dims,
})
match loc {
Some(loc) => loc,
// Create a new atlas
None => {
let (mut atlas, texture) = create_atlas_texture(renderer);
let aabr = atlas
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
.map(aabr_from_alloc_rect)
.unwrap();
let tex_idx = self.textures.len();
let atlas_idx = self.atlases.len();
self.textures.push(texture);
self.atlases.push((atlas, tex_idx));
CacheLoc::Atlas { atlas_idx, aabr }
},
};
// Upload
upload_image(renderer, aabr, &self.textures[idx], &image);
// Insert into cached map
self.cache_map.insert(key, CachedDetails {
location,
valid: true,
});
}
} else {
// Create a texture just for this
let texture = renderer.create_dynamic_texture(dims).unwrap();
let index = self.textures.len();
self.textures.push(texture);
CacheLoc::Texture { index }
};
Some((rotated_aabr(aabr), TexId(idx)))
}
let (idx, aabr) = match location {
CacheLoc::Atlas {
atlas_idx, aabr, ..
} => (self.atlases[atlas_idx].1, aabr),
CacheLoc::Texture { index } => {
(index, Aabr {
min: Vec2::new(0, 0),
// Note texture should always match the cached dimensions
max: dims,
})
},
};
// Upload
upload_image(renderer, aabr, &self.textures[idx], &image);
// Insert into cached map
details.insert(CachedDetails {
location,
valid: true,
});
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 cw180: 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

View File

@ -28,6 +28,7 @@ use crate::{
window::Window,
Error,
};
use ::image::GenericImageView;
use cache::Cache;
use common::{assets, util::srgba_to_linear};
use conrod_core::{
@ -43,6 +44,7 @@ use conrod_core::{
use graphic::{Rotation, TexId};
use log::{error, warn};
use std::{
f32, f64,
fs::File,
io::{BufReader, Read},
ops::Range,
@ -172,6 +174,16 @@ impl Ui {
cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
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 {
image_id,
color,
source_rect: _, // TODO: <-- use this
source_rect,
} => {
let (graphic_id, rotation) = self
.image_map
.get(&image_id)
.expect("Image does not exist in image map");
let graphic_cache = self.cache.graphic_cache_mut();
match graphic_cache.get_graphic(*graphic_id) {
Some(Graphic::Blank) | None => continue,
_ => {},
}
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) {
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 =
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
let gl_aabr = gl_aabr(rect);
let resolution = Vec2::new(
(gl_aabr.size().w * half_res.x).round() as u16,
(gl_aabr.size().h * half_res.y).round() as u16,
(gl_size.w * half_res.x).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.
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
@ -500,7 +554,13 @@ impl Ui {
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 {
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)) {
(_, (false, _)) | (None, (_, true)) => {
let (r, g, b) = (
@ -251,7 +254,7 @@ impl MapConfig {
0.0
})
.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_humidity {
humidity as f64
@ -272,17 +275,22 @@ impl MapConfig {
},
(Some(RiverKind::Ocean), _) => (
0,
((32.0 - water_depth * 32.0) * 1.0) as u8,
((64.0 - water_depth * 64.0) * 1.0) as u8,
((g_water - water_depth * g_water) * 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,
),
(Some(RiverKind::River { .. }), _) => {
(0, 32 + (alt * 95.0) as u8, 64 + (alt * 191.0) as u8, 255)
},
(None, _) | (Some(RiverKind::Lake { .. }), _) => (
0,
(((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8,
(((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8,
(((g_water + water_alt * (127.0 - 32.0)) + (-water_depth * g_water)) * 1.0)
as u8,
(((b_water + water_alt * (255.0 - b_water)) + (-water_depth * b_water))
* 1.0) as u8,
255,
),
};

View File

@ -292,6 +292,9 @@ impl WorldFile {
pub struct WorldSim {
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) locations: Vec<Location>,
@ -1288,6 +1291,7 @@ impl WorldSim {
let mut this = Self {
seed,
max_height: maxh as f32,
chunks,
locations: Vec::new(),
gen_ctx,
@ -1306,7 +1310,11 @@ impl WorldSim {
pub fn get_map(&self) -> Vec<u32> {
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
// 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