Merge branch 'imbris/pixel-art' into 'master'

Make scaling ui elements more pixel art friendly

See merge request veloren/veloren!702
This commit is contained in:
Imbris 2020-01-25 05:09:38 +00:00
commit 659710e2b8
16 changed files with 381 additions and 90 deletions

View File

@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a default map, which is used to speed up starting single player.
- Added a 3D renderered map, which is also used by the server to send the map
to the client.
- Added fullscreen and window size to settings so that they can be persisted
- Added coverage based scaling for pixel art
### Changed

View File

@ -1,4 +1,4 @@
use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelMs9Graphic};
use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelPixArtGraphic};
// TODO: Combine with image_ids, see macro definition
rotation_image_ids! {
@ -104,7 +104,7 @@ image_ids! {
////////////////////////////////////////////////////////////////////////
<VoxelMs9Graphic>
<VoxelPixArtGraphic>
// Skill Icons
twohsword_m1: "voxygen.element.icons.2hsword_m1",
@ -119,7 +119,6 @@ image_ids! {
staff_m2: "voxygen.element.icons.staff_m2",
flyingrod_m1: "voxygen.element.icons.debug_wand_m1",
flyingrod_m2: "voxygen.element.icons.debug_wand_m2",
charge: "voxygen.element.icons.skill_charge_3",

View File

@ -1,4 +1,4 @@
use crate::ui::{Graphic, Transform, Ui};
use crate::ui::{Graphic, SampleStrat, Transform, Ui};
use common::{
assets::{self, watch::ReloadIndicator, Asset},
comp::item::{Armor, Consumable, Ingredient, Item, ItemKind, Tool},
@ -47,7 +47,7 @@ impl ImageSpec {
stretch: false,
..Default::default()
},
None,
SampleStrat::None,
),
ImageSpec::VoxTrans(specifier, offset, [rot_x, rot_y, rot_z], zoom) => Graphic::Voxel(
graceful_load_vox(&specifier),
@ -60,7 +60,7 @@ impl ImageSpec {
orth: true, // TODO: Is this what we want here? @Pfau
stretch: false,
},
None,
SampleStrat::None,
),
}
}

View File

@ -212,6 +212,8 @@ pub enum Event {
ChangeAudioDevice(String),
ChangeMaxFPS(u32),
ChangeFOV(u16),
AdjustWindowSize([u16; 2]),
ToggleFullscreen,
ChangeAaMode(AaMode),
ChangeCloudMode(CloudMode),
ChangeFluidMode(FluidMode),
@ -1758,6 +1760,12 @@ impl Hud {
settings_window::Event::ChangeLanguage(language) => {
events.push(Event::ChangeLanguage(language));
}
settings_window::Event::ToggleFullscreen => {
events.push(Event::ToggleFullscreen);
}
settings_window::Event::AdjustWindowSize(new_size) => {
events.push(Event::AdjustWindowSize(new_size));
}
}
}
}

View File

@ -95,6 +95,9 @@ widget_ids! {
cloud_mode_list,
fluid_mode_text,
fluid_mode_list,
fullscreen_button,
fullscreen_label,
save_window_size_button,
audio_volume_slider,
audio_volume_text,
sfx_volume_slider,
@ -196,6 +199,8 @@ pub enum Event {
ToggleMouseYInvert(bool),
AdjustViewDistance(u32),
AdjustFOV(u16),
AdjustWindowSize([u16; 2]),
ToggleFullscreen,
ChangeAaMode(AaMode),
ChangeCloudMode(CloudMode),
ChangeFluidMode(FluidMode),
@ -1211,7 +1216,7 @@ impl<'a> Widget for SettingsWindow<'a> {
.right_from(state.ids.mouse_zoom_invert_button, 10.0)
.font_size(14)
.font_id(self.fonts.cyri)
.graphics_for(state.ids.button_help)
.graphics_for(state.ids.mouse_zoom_invert_button)
.color(TEXT_COLOR)
.set(state.ids.mouse_zoom_invert_label, ui);
@ -1241,7 +1246,7 @@ impl<'a> Widget for SettingsWindow<'a> {
.right_from(state.ids.mouse_y_invert_button, 10.0)
.font_size(14)
.font_id(self.fonts.cyri)
.graphics_for(state.ids.button_help)
.graphics_for(state.ids.mouse_y_invert_button)
.color(TEXT_COLOR)
.set(state.ids.mouse_y_invert_label, ui);
}
@ -1658,6 +1663,51 @@ impl<'a> Widget for SettingsWindow<'a> {
{
events.push(Event::ChangeFluidMode(mode_list[clicked]));
}
// Fullscreen
Text::new("Fullscreen")
.font_size(14)
.font_id(self.fonts.cyri)
.down_from(state.ids.fluid_mode_list, 8.0)
.color(TEXT_COLOR)
.set(state.ids.fullscreen_label, ui);
let fullscreen = ToggleButton::new(
self.global_state.settings.graphics.fullscreen,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
.w_h(18.0, 18.0)
.right_from(state.ids.fullscreen_label, 10.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.fullscreen_button, ui);
if self.global_state.settings.graphics.fullscreen != fullscreen {
events.push(Event::ToggleFullscreen);
}
// Save current screen size
if Button::image(self.imgs.settings_button)
.w_h(31.0 * 5.0, 12.0 * 2.0)
.hover_image(self.imgs.settings_button_hover)
.press_image(self.imgs.settings_button_press)
.down_from(state.ids.fullscreen_label, 12.0)
.label("Save window size")
.label_font_size(14)
.label_color(TEXT_COLOR)
.label_font_id(self.fonts.cyri)
.set(state.ids.save_window_size_button, ui)
.was_clicked()
{
events.push(Event::AdjustWindowSize(
self.global_state
.window
.logical_size()
.map(|e| e as u16)
.into_array(),
));
}
}
// 5) Sound Tab -----------------------------------

View File

@ -38,7 +38,7 @@ impl PlayState for CharSelectionState {
let mut current_client_state = self.client.borrow().get_client_state();
while let ClientState::Pending | ClientState::Registered = current_client_state {
// Handle window events
for event in global_state.window.fetch_events() {
for event in global_state.window.fetch_events(&mut global_state.settings) {
if self.char_selection_ui.handle_event(event.clone()) {
continue;
}

View File

@ -4,7 +4,7 @@ use crate::{
meta::CharacterData,
render::{Consts, Globals, Renderer},
ui::{
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelMs9Graphic},
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelSs9Graphic},
ImageFrame, ImageSlider, Tooltip, Tooltipable, Ui,
},
GlobalState,
@ -176,7 +176,7 @@ image_ids! {
// Info Window
info_frame: "voxygen.element.frames.info_frame",
<VoxelMs9Graphic>
<VoxelSs9Graphic>
delete_button: "voxygen.element.buttons.x_red",
delete_button_hover: "voxygen.element.buttons.x_red_hover",
delete_button_press: "voxygen.element.buttons.x_red_press",

View File

@ -56,7 +56,7 @@ impl PlayState for MainMenuState {
loop {
// Handle window events.
for event in global_state.window.fetch_events() {
for event in global_state.window.fetch_events(&mut global_state.settings) {
match event {
Event::Close => return PlayStateResult::Shutdown,
// Pass events to ui.

View File

@ -175,7 +175,7 @@ impl PlayState for SessionState {
self.scene.set_select_pos(select_pos);
// Handle window events.
for event in global_state.window.fetch_events() {
for event in global_state.window.fetch_events(&mut global_state.settings) {
// Pass all events to the ui first.
if self.hud.handle_event(event.clone(), global_state) {
continue;
@ -583,6 +583,16 @@ impl PlayState for SessionState {
.unwrap();
localized_strings.log_missing_entries();
}
HudEvent::ToggleFullscreen => {
global_state
.window
.toggle_fullscreen(&mut global_state.settings);
}
HudEvent::AdjustWindowSize(new_size) => {
global_state.window.set_size(new_size.into());
global_state.settings.graphics.window_size = new_size;
global_state.settings.save_to_file_warn();
}
}
}

View File

@ -195,6 +195,8 @@ pub struct GraphicsSettings {
pub aa_mode: AaMode,
pub cloud_mode: CloudMode,
pub fluid_mode: FluidMode,
pub window_size: [u16; 2],
pub fullscreen: bool,
}
impl Default for GraphicsSettings {
@ -206,6 +208,8 @@ impl Default for GraphicsSettings {
aa_mode: AaMode::Fxaa,
cloud_mode: CloudMode::Regular,
fluid_mode: FluidMode::Shiny,
window_size: [1920, 1080],
fullscreen: false,
}
}
}

View File

@ -1,38 +1,22 @@
mod pixel_art;
mod renderer;
pub use renderer::{SampleStrat, Transform};
use crate::render::{Renderer, Texture};
use dot_vox::DotVoxData;
use guillotiere::{size2, SimpleAtlasAllocator};
use hashbrown::HashMap;
use image::{DynamicImage, RgbaImage};
use log::warn;
use pixel_art::resize_pixel_art;
use std::sync::Arc;
use vek::*;
#[derive(Clone)]
pub struct Transform {
pub ori: Quaternion<f32>,
pub offset: Vec3<f32>,
pub zoom: f32,
pub orth: bool,
pub stretch: bool,
}
impl Default for Transform {
fn default() -> Self {
Self {
ori: Quaternion::identity(),
offset: Vec3::zero(),
zoom: 1.0,
orth: true,
stretch: true,
}
}
}
#[derive(Clone)]
pub enum Graphic {
Image(Arc<DynamicImage>),
Voxel(Arc<DotVoxData>, Transform, Option<u8>),
Voxel(Arc<DotVoxData>, Transform, SampleStrat),
Blank,
}
@ -280,20 +264,16 @@ fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2<u16>) -> Op
Some(Graphic::Blank) => None,
// Render image at requested resolution
// TODO: Use source aabr.
Some(Graphic::Image(ref image)) => Some(
image
.resize_exact(
u32::from(dims.x),
u32::from(dims.y),
image::FilterType::Nearest,
)
.to_rgba(),
),
Some(Graphic::Voxel(ref vox, trans, min_samples)) => Some(renderer::draw_vox(
Some(Graphic::Image(ref image)) => Some(resize_pixel_art(
&image.to_rgba(),
u32::from(dims.x),
u32::from(dims.y),
)),
Some(Graphic::Voxel(ref vox, trans, sample_strat)) => Some(renderer::draw_vox(
&vox.as_ref().into(),
dims,
trans.clone(),
*min_samples,
*sample_strat,
)),
None => {
warn!("A graphic was requested via an id which is not in use");

View File

@ -0,0 +1,147 @@
use common::util::{linear_to_srgba, srgba_to_linear};
/// Pixel art scaling
/// Note: The current ui is locked to the pixel grid with little animation, if we want smoothly
/// moving pixel art this should be done in the shaders
/// useful links: https://gitlab.com/veloren/veloren/issues/257
use image::RgbaImage;
use vek::*;
const EPSILON: f32 = 0.0001;
// Averaging colors with alpha such that when blending with the background color the same color
// will be produced as when the individual colors were blended with the background and then
// averaged
// Say we have two areas that we are combining to form a single pixel
// A1 and A2 where these are the fraction of the area of the pixel each color contributes to
// Then if the colors were opaque we would say that the final color ouput color o3 is
// E1: o3 = A1 * o1 + A2 * o2
// where o1 and o2 are the opaque colors of the two areas
// now say the areas are actually translucent and these opaque colors are derived by blending with a
// common backgound color b
// E2: o1 = c1 * a1 + b * (1 - a1)
// E3: o2 = c2 * a2 + b * (1 - a2)
// we want to find the combined color (c3) and combined alpha (a3) such that
// E4: o3 = c3 * a3 + b * (1 - a3)
// substitution of E2 and E3 into E1 gives
// E5: o3 = A1 * (c1 * a1 + b * (1 - a1)) + A2 * (c2 * a2 + b * (1 - a2))
// combining E4 and E5 then separting like terms into separte equations gives
// E6: c3 * a3 = A1 * c1 * a1 + A2 * c2 * a2
// E7: b * (1 - a3) = A1 * b * (1 - a1) + A2 * b * (1 - a2)
// dropping b from E7 and solving for a3
// E8: a3 = 1 - A1 * (1 - a1) + A2 * (1 - a2)
// we can now calculate the combined alpha value
// and E6 can then be solved for c3
// E9: c3 = (A1 * c1 * a1 + A2 * c2 * a2) / a3
pub fn resize_pixel_art(image: &RgbaImage, new_width: u32, new_height: u32) -> RgbaImage {
let (width, height) = image.dimensions();
let mut new_image = RgbaImage::new(new_width, new_height);
// Ratio of old image dimensions to new dimensions
// Also the sampling dimensions within the old image for a single pixel in the new image
let wratio = width as f32 / new_width as f32;
let hratio = height as f32 / new_height as f32;
for x in 0..new_width {
// Calculate sampling strategy
let xsmin = x as f32 * wratio;
let xsmax = xsmin + wratio;
// Min and max pixels covered
let xminp = xsmin.floor() as u32;
let xmaxp = ((xsmax - EPSILON).ceil() as u32)
.checked_sub(1)
.unwrap_or(0);
// Fraction of first pixel to use
let first_x_frac = if xminp != xmaxp {
1.0 - xsmin.fract()
} else {
xsmax - xsmin
};
let last_x_frac = xsmax - xmaxp as f32;
for y in 0..new_height {
// Calculate sampling strategy
let ysmin = y as f32 * hratio;
let ysmax = ysmin + hratio;
// Min and max of pixels covered
let yminp = ysmin.floor() as u32;
let ymaxp = ((ysmax - EPSILON).ceil() as u32)
.checked_sub(1)
.unwrap_or(0);
// Fraction of first pixel to use
let first_y_frac = if yminp != ymaxp {
1.0 - ysmin.fract()
} else {
ysmax - ysmin
};
let last_y_frac = ysmax - ymaxp as f32;
let mut linear_color = Rgba::new(0.0, 0.0, 0.0, wratio * hratio);
// Left column
// First pixel sample (top left assuming that is the origin)
linear_color += get_linear_with_frac(image, xminp, yminp, first_x_frac * first_y_frac);
// Left edge
for j in yminp + 1..ymaxp {
linear_color += get_linear_with_frac(image, xminp, j, first_x_frac);
}
// Bottom left corner
if yminp != ymaxp {
linear_color +=
get_linear_with_frac(image, xminp, ymaxp, first_x_frac * last_y_frac);
}
// Interior columns
for i in xminp + 1..xmaxp {
// Top edge
linear_color += get_linear_with_frac(image, i, yminp, first_y_frac);
// Inner (entire pixel is encompassed by sample)
for j in yminp + 1..ymaxp {
linear_color += get_linear_with_frac(image, i, j, 1.0);
}
// Bottom edge
if yminp != ymaxp {
linear_color += get_linear_with_frac(image, xminp, ymaxp, last_y_frac);
}
}
// Right column
if xminp != xmaxp {
// Top right corner
linear_color +=
get_linear_with_frac(image, xmaxp, yminp, first_y_frac * last_x_frac);
// Right edge
for j in yminp + 1..ymaxp {
linear_color += get_linear_with_frac(image, xmaxp, j, last_x_frac);
}
// Bottom right corner
if yminp != ymaxp {
linear_color +=
get_linear_with_frac(image, xmaxp, ymaxp, last_x_frac * last_y_frac);
}
}
// Divide summed color by area sample covers and convert back to srgb
// I wonder if precalulating the inverse of these divs would have a significant effect
linear_color = linear_color / wratio / hratio;
linear_color =
Rgba::from_translucent(linear_color.rgb() / linear_color.a, linear_color.a);
new_image.put_pixel(
x,
y,
image::Rgba(
linear_to_srgba(linear_color)
.map(|e| (e * 255.0).round() as u8)
.into_array(),
),
);
}
}
new_image
}
fn get_linear(image: &RgbaImage, x: u32, y: u32) -> Rgba<f32> {
srgba_to_linear(Rgba::<u8>::from(image.get_pixel(x, y).0).map(|e| e as f32 / 255.0))
}
// See comments above resize_pixel_art
fn get_linear_with_frac(image: &RgbaImage, x: u32, y: u32, frac: f32) -> Rgba<f32> {
let rgba = get_linear(image, x, y);
let adjusted_rgb = rgba.rgb() * rgba.a * frac;
let adjusted_alpha = -frac * (1.0 - rgba.a);
Rgba::from_translucent(adjusted_rgb, adjusted_alpha)
}

View File

@ -1,4 +1,3 @@
use super::Transform;
use common::{
figure::Segment,
util::{linear_to_srgba, srgba_to_linear},
@ -8,6 +7,33 @@ use euc::{buffer::Buffer2d, rasterizer, Pipeline};
use image::{DynamicImage, RgbaImage};
use vek::*;
#[derive(Copy, Clone)]
pub enum SampleStrat {
None,
SuperSampling(u8),
PixelCoverage,
}
#[derive(Clone)]
pub struct Transform {
pub ori: Quaternion<f32>,
pub offset: Vec3<f32>,
pub zoom: f32,
pub orth: bool,
pub stretch: bool,
}
impl Default for Transform {
fn default() -> Self {
Self {
ori: Quaternion::identity(),
offset: Vec3::zero(),
zoom: 1.0,
orth: true,
stretch: true,
}
}
}
struct Voxel {
mvp: Mat4<f32>,
}
@ -48,7 +74,7 @@ impl<'a> Pipeline for Voxel {
) -> ([f32; 3], Self::VsOut) {
let light = Rgba::from_opaque(Rgb::from(*ao_level as f32 / 4.0 + 0.25));
let color = light * srgba_to_linear(Rgba::from_opaque(*col));
let position = Vec3::from(self.mvp * Vec4::from_point(*pos)).into_array();
let position = (self.mvp * Vec4::from_point(*pos)).xyz().into_array();
(position, color)
}
#[inline(always)]
@ -63,10 +89,33 @@ pub fn draw_vox(
segment: &Segment,
output_size: Vec2<u16>,
transform: Transform,
min_samples: Option<u8>,
sample_strat: SampleStrat,
) -> RgbaImage {
let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize;
let dims = output_size.map(|e| e as usize * scale).into_array();
let output_size = output_size.map(|e| e as usize);
let ori_mat = Mat4::from(transform.ori);
let rotated_segment_dims = (ori_mat * Vec4::from_direction(segment.size().map(|e| e as f32)))
.xyz()
.map(|e| e.abs());
let dims = match sample_strat {
SampleStrat::None => output_size,
SampleStrat::SuperSampling(min_samples) => {
output_size * (min_samples as f32).sqrt().ceil() as usize
}
// Assumes
// - rotations are multiples of 90 degrees
// - the projection is orthographic
// - no translation or zooming is performed
// - stretch is enabled
SampleStrat::PixelCoverage => Vec2::new(
rotated_segment_dims.x.round() as usize,
rotated_segment_dims.y.round() as usize,
),
}
.into_array();
// Rendering buffers
let mut color = Buffer2d::new(dims, [0; 4]);
let mut depth = Buffer2d::new(dims, 1.0);
@ -92,13 +141,13 @@ pub fn draw_vox(
} * Mat4::scaling_3d(
// TODO replace with camera-like parameters?
if transform.stretch {
Vec3::new(2.0 / w, 2.0 / d, 2.0 / h) // Only works with flipped models :(
rotated_segment_dims.map(|e| 2.0 / e)
} else {
let s = w.max(h).max(d);
Vec3::new(2.0 / s, 2.0 / s, 2.0 / s)
Vec3::from(2.0 / s)
} * transform.zoom,
) * Mat4::translation_3d(transform.offset)
* Mat4::from(transform.ori)
* ori_mat
* Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
Voxel { mvp }.draw::<rasterizer::Triangles<_>, _>(
@ -107,29 +156,33 @@ pub fn draw_vox(
&mut depth,
);
let image = DynamicImage::ImageRgba8(
RgbaImage::from_vec(
dims[0] as u32,
dims[1] as u32,
color
.as_ref()
.iter()
.flatten()
.cloned()
.collect::<Vec<u8>>(),
)
.unwrap(),
);
if scale > 1 {
image.resize_exact(
let rgba_img = RgbaImage::from_vec(
dims[0] as u32,
dims[1] as u32,
color
.as_ref()
.iter()
.flatten()
.copied()
.collect::<Vec<u8>>(),
)
.unwrap();
match sample_strat {
SampleStrat::None => rgba_img,
SampleStrat::SuperSampling(_) => DynamicImage::ImageRgba8(rgba_img)
.resize_exact(
output_size.x as u32,
output_size.y as u32,
image::FilterType::Triangle,
)
.to_rgba(),
SampleStrat::PixelCoverage => super::pixel_art::resize_pixel_art(
&rgba_img,
output_size.x as u32,
output_size.y as u32,
image::FilterType::Triangle,
)
} else {
image
),
}
.to_rgba()
}
fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 {

View File

@ -1,4 +1,4 @@
use super::{Graphic, Transform};
use super::{Graphic, SampleStrat, Transform};
use common::assets::{load, Error};
use dot_vox::DotVoxData;
use image::DynamicImage;
@ -25,9 +25,10 @@ impl<'a> GraphicCreator<'a> for ImageGraphic {
}
pub enum VoxelGraphic {}
pub enum VoxelMsGraphic {}
pub enum VoxelMs4Graphic {}
pub enum VoxelMs9Graphic {}
pub enum VoxelSsGraphic {}
pub enum VoxelSs4Graphic {}
pub enum VoxelSs9Graphic {}
pub enum VoxelPixArtGraphic {}
impl<'a> GraphicCreator<'a> for VoxelGraphic {
type Specifier = &'a str;
@ -38,11 +39,11 @@ impl<'a> GraphicCreator<'a> for VoxelGraphic {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
None,
SampleStrat::None,
))
}
}
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
impl<'a> GraphicCreator<'a> for VoxelSsGraphic {
type Specifier = (&'a str, u8);
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
@ -51,11 +52,11 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(specifier.1),
SampleStrat::SuperSampling(specifier.1),
))
}
}
impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
impl<'a> GraphicCreator<'a> for VoxelSs4Graphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
@ -64,11 +65,11 @@ impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(4),
SampleStrat::SuperSampling(4),
))
}
}
impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
impl<'a> GraphicCreator<'a> for VoxelSs9Graphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
@ -77,7 +78,20 @@ impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(9),
SampleStrat::SuperSampling(9),
))
}
}
impl<'a> GraphicCreator<'a> for VoxelPixArtGraphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier)?,
Transform {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
SampleStrat::PixelCoverage,
))
}
}

View File

@ -9,7 +9,7 @@ pub mod img_ids;
mod font_ids;
pub use event::Event;
pub use graphic::{Graphic, Transform};
pub use graphic::{Graphic, SampleStrat, Transform};
pub use scale::{Scale, ScaleMode};
pub use widgets::{
image_frame::ImageFrame,

View File

@ -287,9 +287,14 @@ impl Window {
pub fn new(settings: &Settings) -> Result<Window, Error> {
let events_loop = glutin::EventsLoop::new();
let size = settings.graphics.window_size;
let win_builder = glutin::WindowBuilder::new()
.with_title("Veloren")
.with_dimensions(glutin::dpi::LogicalSize::new(1920.0, 1080.0))
.with_dimensions(glutin::dpi::LogicalSize::new(
size[0] as f64,
size[1] as f64,
))
.with_maximized(true);
let ctx_builder = glutin::ContextBuilder::new()
@ -413,7 +418,7 @@ impl Window {
let keypress_map = HashMap::new();
Ok(Self {
let mut this = Self {
events_loop,
renderer: Renderer::new(
device,
@ -436,7 +441,11 @@ impl Window {
keypress_map,
supplement_events: vec![],
focused: true,
})
};
this.fullscreen(settings.graphics.fullscreen);
Ok(this)
}
pub fn renderer(&self) -> &Renderer {
@ -446,7 +455,7 @@ impl Window {
&mut self.renderer
}
pub fn fetch_events(&mut self) -> Vec<Event> {
pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
let mut events = vec![];
events.append(&mut self.supplement_events);
// Refresh ui size (used when changing playstates)
@ -596,7 +605,7 @@ impl Window {
}
if toggle_fullscreen {
self.fullscreen(!self.is_fullscreen());
self.toggle_fullscreen(settings);
}
events
@ -618,6 +627,12 @@ impl Window {
let _ = self.window.window().grab_cursor(grab);
}
pub fn toggle_fullscreen(&mut self, settings: &mut Settings) {
self.fullscreen(!self.is_fullscreen());
settings.graphics.fullscreen = self.is_fullscreen();
settings.save_to_file_warn();
}
pub fn is_fullscreen(&self) -> bool {
self.fullscreen
}
@ -646,6 +661,15 @@ impl Window {
Vec2::new(w, h)
}
pub fn set_size(&mut self, new_size: Vec2<u16>) {
self.window
.window()
.set_inner_size(glutin::dpi::LogicalSize::new(
new_size.x as f64,
new_size.y as f64,
));
}
pub fn send_supplement_event(&mut self, event: Event) {
self.supplement_events.push(event)
}