Enable pixel art resizing for voxel based ui elements

This commit is contained in:
Imbris 2020-01-02 22:51:07 -05:00
parent e5d841e62f
commit 460f5e6f26
9 changed files with 127 additions and 78 deletions

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

@ -1668,7 +1668,7 @@ impl<'a> Widget for SettingsWindow<'a> {
Text::new("Fullscreen")
.font_size(14)
.font_id(self.fonts.cyri)
.down_from(state.ids.fluid_mode_list, 125.0)
.down_from(state.ids.fluid_mode_list, 8.0)
.color(TEXT_COLOR)
.set(state.ids.fullscreen_label, ui);

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

@ -1,6 +1,8 @@
mod pixel_art;
mod renderer;
pub use renderer::{SampleStrat, Transform};
use crate::render::{Renderer, Texture};
use dot_vox::DotVoxData;
use guillotiere::{size2, SimpleAtlasAllocator};
@ -11,30 +13,10 @@ 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,
}
@ -287,11 +269,11 @@ fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2<u16>) -> Op
u32::from(dims.x),
u32::from(dims.y),
)),
Some(Graphic::Voxel(ref vox, trans, min_samples)) => Some(renderer::draw_vox(
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

@ -2,6 +2,7 @@ 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::*;
@ -17,16 +18,16 @@ const EPSILON: f32 = 0.0001;
// 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 + g * (1 - a1)
// E3: o2 = c2 * a2 + g * (1 - a2)
// 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 + g * (1 - a3)
// E4: o3 = c3 * a3 + b * (1 - a3)
// substitution of E2 and E3 into E1 gives
// E5: o3 = A1 * (c1 * a1 + g * (1 - a1)) + A2 * (c2 * a2 + g * (1 - a2))
// 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: g * (1 - a3) = A1 * g * (1 - a1) + A2 * g * (1 - a2)
// dropping g from E7 and solving for a3
// 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

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,