Add a method to convert cursor coords to pixel coords (#77)
* Add a method to convert cursor coords to pixel coords * Add method `window_pos_to_pixel` to Pixels struct * Converts cursor / window physical coordinates to pixel coords * Fix formatting * Return result rather than clamping pixel coordinates * Use transformation matrices to convert from cursor coord to pixel * Adds a struct ScalingMatrix that manages the creation and usage of the transformation matrix used in the renderer. * Added an inverse function on ScalingMatrix - 4x4 matrix inverse (This should probably use a library, but it doesn't seem worth adding a dependancy for one function) * Optimize matrix multiplication for cursor position calculation * Use ultraviolet for matrix and vector math * Add suggested changes This keeps the split between usize and isize This also changes the input cursor position to f32, because it was immediately cast to an f32 for the transformations.
This commit is contained in:
parent
b43336d45d
commit
c96e46d3d4
|
@ -22,6 +22,7 @@ include = [
|
||||||
thiserror = "1.0.15"
|
thiserror = "1.0.15"
|
||||||
wgpu = "0.5.0"
|
wgpu = "0.5.0"
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
|
ultraviolet = "0.4.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pixels-mocks = { path = "pixels-mocks" }
|
pixels-mocks = { path = "pixels-mocks" }
|
||||||
|
|
|
@ -66,13 +66,19 @@ fn main() -> Result<(), Error> {
|
||||||
let (dx, dy) = input.mouse_diff();
|
let (dx, dy) = input.mouse_diff();
|
||||||
let prev_x = mx - dx;
|
let prev_x = mx - dx;
|
||||||
let prev_y = my - dy;
|
let prev_y = my - dy;
|
||||||
let dpx = hidpi_factor as f32;
|
|
||||||
let (w, h) = (p_width as f32 / dpx, p_height as f32 / dpx);
|
let (mx_i, my_i) = pixels
|
||||||
let mx_i = ((mx / w) * (SCREEN_WIDTH as f32)).round() as isize;
|
.window_pos_to_pixel((mx, my))
|
||||||
let my_i = ((my / h) * (SCREEN_HEIGHT as f32)).round() as isize;
|
.unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
|
||||||
let px_i = ((prev_x / w) * (SCREEN_WIDTH as f32)).round() as isize;
|
|
||||||
let py_i = ((prev_y / h) * (SCREEN_HEIGHT as f32)).round() as isize;
|
let (px_i, py_i) = pixels
|
||||||
((mx_i, my_i), (px_i, py_i))
|
.window_pos_to_pixel((prev_x, prev_y))
|
||||||
|
.unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
|
||||||
|
|
||||||
|
(
|
||||||
|
(mx_i as isize, my_i as isize),
|
||||||
|
(px_i as isize, py_i as isize),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
|
116
src/lib.rs
116
src/lib.rs
|
@ -56,6 +56,10 @@ pub struct Pixels {
|
||||||
texture_extent: wgpu::Extent3d,
|
texture_extent: wgpu::Extent3d,
|
||||||
texture_format_size: u32,
|
texture_format_size: u32,
|
||||||
pixels: Vec<u8>,
|
pixels: Vec<u8>,
|
||||||
|
|
||||||
|
// The inverse of the scaling matrix used by the renderer
|
||||||
|
// Used to convert physical coordinates back to pixel coordinates (for the mouse)
|
||||||
|
scaling_matrix_inverse: ultraviolet::Mat4,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder to help create customized pixel buffers.
|
/// A builder to help create customized pixel buffers.
|
||||||
|
@ -161,6 +165,17 @@ impl Pixels {
|
||||||
self.surface_texture.width = width;
|
self.surface_texture.width = width;
|
||||||
self.surface_texture.height = height;
|
self.surface_texture.height = height;
|
||||||
|
|
||||||
|
// Update ScalingMatrix for mouse transformation
|
||||||
|
self.scaling_matrix_inverse = renderers::ScalingMatrix::new(
|
||||||
|
(
|
||||||
|
self.texture_extent.width as f32,
|
||||||
|
self.texture_extent.height as f32,
|
||||||
|
),
|
||||||
|
(width as f32, height as f32),
|
||||||
|
)
|
||||||
|
.transform
|
||||||
|
.inversed();
|
||||||
|
|
||||||
// Recreate the swap chain
|
// Recreate the swap chain
|
||||||
self.swap_chain = self.device.create_swap_chain(
|
self.swap_chain = self.device.create_swap_chain(
|
||||||
&self.surface_texture.surface,
|
&self.surface_texture.surface,
|
||||||
|
@ -263,6 +278,99 @@ impl Pixels {
|
||||||
pub fn get_frame(&mut self) -> &mut [u8] {
|
pub fn get_frame(&mut self) -> &mut [u8] {
|
||||||
&mut self.pixels
|
&mut self.pixels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the pixel location from a physical location on the window,
|
||||||
|
/// dealing with window resizing, scaling, and margins. Takes a physical
|
||||||
|
/// position (x, y) within the window, and returns a pixel position (x, y).
|
||||||
|
///
|
||||||
|
/// The location must be given in physical units (for example, winit's `PhysicalLocation`)
|
||||||
|
///
|
||||||
|
/// If the given physical position is outside of the drawing area, this
|
||||||
|
/// function returns an `Err` value with the pixel coordinates outside of
|
||||||
|
/// the screen, using isize instead of usize.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use pixels::Pixels;
|
||||||
|
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH);
|
||||||
|
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface);
|
||||||
|
/// const WIDTH: u32 = 320;
|
||||||
|
/// const HEIGHT: u32 = 240;
|
||||||
|
///
|
||||||
|
/// let mut pixels = Pixels::new(WIDTH, HEIGHT, surface_texture)?;
|
||||||
|
///
|
||||||
|
/// // A cursor position in physical units
|
||||||
|
/// let cursor_position: (f32, f32) = winit::dpi::PhysicalPosition::new(0.0, 0.0).into();
|
||||||
|
///
|
||||||
|
/// // Convert it to a pixel location
|
||||||
|
/// let pixel_position: (usize, usize) = pixels.window_pos_to_pixel(cursor_position)
|
||||||
|
/// // Clamp the output to within the screen
|
||||||
|
/// .unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
|
||||||
|
/// # Ok::<(), pixels::Error>(())
|
||||||
|
/// ```
|
||||||
|
pub fn window_pos_to_pixel(
|
||||||
|
&self,
|
||||||
|
physical_position: (f32, f32),
|
||||||
|
) -> Result<(usize, usize), (isize, isize)> {
|
||||||
|
let physical_width = self.surface_texture.width as f32;
|
||||||
|
let physical_height = self.surface_texture.height as f32;
|
||||||
|
|
||||||
|
let pixels_width = self.texture_extent.width as f32;
|
||||||
|
let pixels_height = self.texture_extent.height as f32;
|
||||||
|
|
||||||
|
let pos = ultraviolet::Vec4::new(
|
||||||
|
(physical_position.0 / physical_width - 0.5) * pixels_width,
|
||||||
|
(physical_position.1 / physical_height - 0.5) * pixels_height,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pos = self.scaling_matrix_inverse * pos;
|
||||||
|
|
||||||
|
let pos = (
|
||||||
|
pos.x / pos.w + pixels_width / 2.0,
|
||||||
|
-pos.y / pos.w + pixels_height / 2.0,
|
||||||
|
);
|
||||||
|
let pixel_x = pos.0.floor() as isize;
|
||||||
|
let pixel_y = pos.1.floor() as isize;
|
||||||
|
|
||||||
|
if pixel_x < 0
|
||||||
|
|| pixel_x >= self.texture_extent.width as isize
|
||||||
|
|| pixel_y < 0
|
||||||
|
|| pixel_y >= self.texture_extent.height as isize
|
||||||
|
{
|
||||||
|
Err((pixel_x, pixel_y))
|
||||||
|
} else {
|
||||||
|
Ok((pixel_x as usize, pixel_y as usize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clamp a pixel position to the pixel buffer size.
|
||||||
|
///
|
||||||
|
/// This can be used to clamp the `Err` value returned by [`Pixels::window_pos_to_pixel`]
|
||||||
|
/// to a position clamped within the drawing area.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use pixels::Pixels;
|
||||||
|
/// # let surface = wgpu::Surface::create(&pixels_mocks::RWH);
|
||||||
|
/// # let surface_texture = pixels::SurfaceTexture::new(1024, 768, surface);
|
||||||
|
/// const WIDTH: u32 = 320;
|
||||||
|
/// const HEIGHT: u32 = 240;
|
||||||
|
///
|
||||||
|
/// let mut pixels = Pixels::new(WIDTH, HEIGHT, surface_texture)?;
|
||||||
|
///
|
||||||
|
/// let pixel_pos = pixels.clamp_pixel_pos((-19, 20));
|
||||||
|
/// assert_eq!(pixel_pos, (0, 20));
|
||||||
|
///
|
||||||
|
/// let pixel_pos = pixels.clamp_pixel_pos((11, 3000));
|
||||||
|
/// assert_eq!(pixel_pos, (11, 240));
|
||||||
|
/// # Ok::<(), pixels::Error>(())
|
||||||
|
/// ```
|
||||||
|
pub fn clamp_pixel_pos(&self, pos: (isize, isize)) -> (usize, usize) {
|
||||||
|
(
|
||||||
|
pos.0.max(0).min(self.texture_extent.width as isize - 1) as usize,
|
||||||
|
pos.1.max(0).min(self.texture_extent.height as isize - 1) as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'req> PixelsBuilder<'req> {
|
impl<'req> PixelsBuilder<'req> {
|
||||||
|
@ -508,6 +616,13 @@ impl<'req> PixelsBuilder<'req> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let scaling_matrix_inverse = renderers::ScalingMatrix::new(
|
||||||
|
(width as f32, height as f32),
|
||||||
|
(surface_texture.width as f32, surface_texture.height as f32),
|
||||||
|
)
|
||||||
|
.transform
|
||||||
|
.inversed();
|
||||||
|
|
||||||
// Create a renderer that impls `RenderPass`
|
// Create a renderer that impls `RenderPass`
|
||||||
let mut renderers = vec![Renderer::factory(
|
let mut renderers = vec![Renderer::factory(
|
||||||
device.clone(),
|
device.clone(),
|
||||||
|
@ -538,6 +653,7 @@ impl<'req> PixelsBuilder<'req> {
|
||||||
texture_extent,
|
texture_extent,
|
||||||
texture_format_size,
|
texture_format_size,
|
||||||
pixels,
|
pixels,
|
||||||
|
scaling_matrix_inverse,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
106
src/renderers.rs
106
src/renderers.rs
|
@ -1,5 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use ultraviolet::Mat4;
|
||||||
use wgpu::{self, Extent3d, TextureView};
|
use wgpu::{self, Extent3d, TextureView};
|
||||||
|
|
||||||
use crate::include_spv;
|
use crate::include_spv;
|
||||||
|
@ -41,25 +42,17 @@ impl Renderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create uniform buffer
|
// Create uniform buffer
|
||||||
#[rustfmt::skip]
|
// TODO: This should also have the width / height of the of the window surface,
|
||||||
let transform: [f32; 16] = [
|
// so that it won't break when the window is created with a different size.
|
||||||
1.0, 0.0, 0.0, 0.0,
|
let matrix = ScalingMatrix::new(
|
||||||
0.0, -1.0, 0.0, 0.0,
|
(texture_size.width as f32, texture_size.height as f32),
|
||||||
0.0, 0.0, 1.0, 0.0,
|
(texture_size.width as f32, texture_size.height as f32),
|
||||||
0.0, 0.0, 0.0, 1.0,
|
);
|
||||||
];
|
let transform_bytes = matrix.as_bytes();
|
||||||
let mut tranform_bytes: Vec<u8> = Vec::with_capacity(4 * transform.len());
|
let uniform_buffer = device.create_buffer_with_data(
|
||||||
transform
|
&transform_bytes,
|
||||||
.iter()
|
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
.map(|e| e.to_bits().to_ne_bytes())
|
);
|
||||||
.for_each(|b| tranform_bytes.extend(b.iter()));
|
|
||||||
let mapped = device.create_buffer_mapped(&wgpu::BufferDescriptor {
|
|
||||||
label: None,
|
|
||||||
size: tranform_bytes.len() as u64,
|
|
||||||
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
|
||||||
});
|
|
||||||
mapped.data.copy_from_slice(&tranform_bytes);
|
|
||||||
let uniform_buffer = mapped.finish();
|
|
||||||
|
|
||||||
// Create bind group
|
// Create bind group
|
||||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
@ -176,38 +169,12 @@ impl RenderPass for Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, encoder: &mut wgpu::CommandEncoder, width: u32, height: u32) {
|
fn resize(&mut self, encoder: &mut wgpu::CommandEncoder, width: u32, height: u32) {
|
||||||
let width = width as f32;
|
let matrix = ScalingMatrix::new((self.width, self.height), (width as f32, height as f32));
|
||||||
let height = height as f32;
|
let transform_bytes = matrix.as_bytes();
|
||||||
|
|
||||||
// Get smallest scale size
|
let temp_buf = self
|
||||||
let scale = (width / self.width)
|
.device
|
||||||
.min(height / self.height)
|
.create_buffer_with_data(&transform_bytes, wgpu::BufferUsage::COPY_SRC);
|
||||||
.max(1.0)
|
|
||||||
.floor();
|
|
||||||
|
|
||||||
// Update transformation matrix
|
|
||||||
let sw = self.width * scale / width;
|
|
||||||
let sh = self.height * scale / height;
|
|
||||||
#[rustfmt::skip]
|
|
||||||
let transform: [f32; 16] = [
|
|
||||||
sw, 0.0, 0.0, 0.0,
|
|
||||||
0.0, -sh, 0.0, 0.0,
|
|
||||||
0.0, 0.0, 1.0, 0.0,
|
|
||||||
0.0, 0.0, 0.0, 1.0,
|
|
||||||
];
|
|
||||||
let mut tranform_bytes: Vec<u8> = Vec::with_capacity(4 * transform.len());
|
|
||||||
transform
|
|
||||||
.iter()
|
|
||||||
.map(|e| e.to_bits().to_ne_bytes())
|
|
||||||
.for_each(|b| tranform_bytes.extend(b.iter()));
|
|
||||||
let mapped = self.device.create_buffer_mapped(&wgpu::BufferDescriptor {
|
|
||||||
label: None,
|
|
||||||
size: tranform_bytes.len() as u64,
|
|
||||||
usage: wgpu::BufferUsage::COPY_SRC,
|
|
||||||
});
|
|
||||||
mapped.data.copy_from_slice(&tranform_bytes);
|
|
||||||
let temp_buf = mapped.finish();
|
|
||||||
encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buffer, 0, 64);
|
|
||||||
encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buffer, 0, 64);
|
encoder.copy_buffer_to_buffer(&temp_buf, 0, &self.uniform_buffer, 0, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,3 +186,42 @@ impl RenderPass for Renderer {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ScalingMatrix {
|
||||||
|
pub(crate) transform: Mat4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScalingMatrix {
|
||||||
|
// texture_size is the dimensions of the drawing texture
|
||||||
|
// screen_size is the dimensions of the surface being drawn to
|
||||||
|
pub(crate) fn new(texture_size: (f32, f32), screen_size: (f32, f32)) -> ScalingMatrix {
|
||||||
|
let (screen_width, screen_height) = screen_size;
|
||||||
|
let (texture_width, texture_height) = texture_size;
|
||||||
|
|
||||||
|
// Get smallest scale size
|
||||||
|
let scale = (screen_width / texture_width)
|
||||||
|
.min(screen_height / texture_height)
|
||||||
|
.max(1.0)
|
||||||
|
.floor();
|
||||||
|
|
||||||
|
// Update transformation matrix
|
||||||
|
let sw = texture_width * scale / screen_width;
|
||||||
|
let sh = texture_height * scale / screen_height;
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let transform: [f32; 16] = [
|
||||||
|
sw, 0.0, 0.0, 0.0,
|
||||||
|
0.0, -sh, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
ScalingMatrix {
|
||||||
|
transform: Mat4::from(transform),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.transform.as_byte_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue