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 1a61d2f154
commit 7702df5d8d
27 changed files with 438 additions and 257 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

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