element_selector.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
Copyright (C) 2021 Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::rc::Rc;
use super::{DataPtr, Device, DrawArea, DrawType, Queue, ViewPtr};
use crate::utils;
use futures::executor;
use iced_wgpu::wgpu;
use iced_winit::winit::dpi::{PhysicalPosition, PhysicalSize};
use utils::BufferDimensions;
pub struct ElementSelector {
device: Rc<Device>,
queue: Rc<Queue>,
readers: Vec<SceneReader>,
window_size: PhysicalSize<u32>,
view: ViewPtr,
data: DataPtr,
area: DrawArea,
}
impl ElementSelector {
pub fn new(
device: Rc<Device>,
queue: Rc<Queue>,
window_size: PhysicalSize<u32>,
view: ViewPtr,
data: DataPtr,
area: DrawArea,
) -> Self {
let readers = vec![
SceneReader::new(DrawType::Widget),
SceneReader::new(DrawType::Grid),
SceneReader::new(DrawType::Design),
SceneReader::new(DrawType::Phantom),
];
Self {
device,
queue,
window_size,
readers,
view,
data,
area,
}
}
pub fn resize(&mut self, window_size: PhysicalSize<u32>, area: DrawArea) {
self.area = area;
self.window_size = window_size;
}
pub fn set_selected_id(
&mut self,
clicked_pixel: PhysicalPosition<f64>,
) -> Option<SceneElement> {
if self.readers[0].pixels.is_none() || self.view.borrow().need_redraw_fake() {
for i in 0..self.readers.len() {
let pixels = self.update_fake_pixels(self.readers[i].draw_type);
self.readers[i].pixels = Some(pixels)
}
}
self.get_highest_priority_element(clicked_pixel)
}
fn get_highest_priority_element(
&self,
clicked_pixel: PhysicalPosition<f64>,
) -> Option<SceneElement> {
let pixel = (
clicked_pixel.cast::<u32>().x.min(self.area.size.width - 1) + self.area.position.x,
clicked_pixel.cast::<u32>().y.min(self.area.size.height - 1) + self.area.position.y,
);
for max_delta in 0..=5 {
let min_x = pixel.0.max(max_delta) - max_delta;
let max_x = (pixel.0 + max_delta).min(self.window_size.width - 1);
let min_y = pixel.1.max(max_delta) - max_delta;
let max_y = (pixel.1 + max_delta).min(self.window_size.height - 1);
for x in min_x..=max_x {
for y in min_y..=max_y {
let byte0 =
(y * self.window_size.width + x) as usize * std::mem::size_of::<u32>();
for reader in self.readers.iter() {
if let Some(element) = reader.read_pixel(byte0) {
return Some(element);
}
}
}
}
}
None
}
fn update_fake_pixels(&self, draw_type: DrawType) -> Vec<u8> {
let size = wgpu::Extent3d {
width: self.window_size.width,
height: self.window_size.height,
depth: 1,
};
let (texture, texture_view) = self.create_fake_scene_texture(self.device.as_ref(), size);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
self.view.borrow_mut().draw(
&mut encoder,
&texture_view,
draw_type,
self.area,
self.data.borrow().get_action_mode(),
);
// create a buffer and fill it with the texture
let extent = wgpu::Extent3d {
width: size.width,
height: size.height,
depth: 1,
};
let buffer_dimensions =
BufferDimensions::new(extent.width as usize, extent.height as usize);
let buf_size = buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height;
let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
size: buf_size as u64,
usage: wgpu::BufferUsage::MAP_READ | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false,
label: Some("staging_buffer"),
});
let buffer_copy_view = wgpu::BufferCopyView {
buffer: &staging_buffer,
layout: wgpu::TextureDataLayout {
offset: 0,
bytes_per_row: buffer_dimensions.padded_bytes_per_row as u32,
rows_per_image: 0,
},
};
let origin = wgpu::Origin3d { x: 0, y: 0, z: 0 };
let texture_copy_view = wgpu::TextureCopyView {
texture: &texture,
mip_level: 0,
origin,
};
encoder.copy_texture_to_buffer(texture_copy_view, buffer_copy_view, extent);
self.queue.submit(Some(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
self.device.poll(wgpu::Maintain::Wait);
let pixels = async {
if let Ok(()) = buffer_future.await {
let pixels_slice = buffer_slice.get_mapped_range();
let mut pixels = Vec::with_capacity((size.height * size.width) as usize);
for chunck in pixels_slice.chunks(buffer_dimensions.padded_bytes_per_row) {
for byte in chunck[..buffer_dimensions.unpadded_bytes_per_row].iter() {
pixels.push(*byte);
}
}
drop(pixels_slice);
staging_buffer.unmap();
pixels
} else {
panic!("could not read fake texture");
}
};
executor::block_on(pixels)
}
fn create_fake_scene_texture(
&self,
device: &Device,
size: wgpu::Extent3d,
) -> (wgpu::Texture, wgpu::TextureView) {
let desc = wgpu::TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8Unorm,
usage: wgpu::TextureUsage::RENDER_ATTACHMENT
| wgpu::TextureUsage::SAMPLED
| wgpu::TextureUsage::COPY_SRC,
label: Some("desc"),
};
let texture_view_descriptor = wgpu::TextureViewDescriptor {
label: Some("texture_view_descriptor"),
format: Some(wgpu::TextureFormat::Bgra8Unorm),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
level_count: None,
base_array_layer: 0,
array_layer_count: None,
};
let texture = device.create_texture(&desc);
let view = texture.create_view(&texture_view_descriptor);
(texture, view)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SceneElement {
DesignElement(u32, u32),
WidgetElement(u32),
PhantomElement(utils::PhantomElement),
Grid(u32, usize),
GridCircle(u32, usize, isize, isize),
}
impl SceneElement {
pub fn get_design(&self) -> Option<u32> {
match self {
SceneElement::DesignElement(d, _) => Some(*d),
SceneElement::WidgetElement(_) => None,
SceneElement::PhantomElement(p) => Some(p.design_id),
SceneElement::Grid(d, _) => Some(*d),
SceneElement::GridCircle(d, _, _, _) => Some(*d),
}
}
#[allow(dead_code)]
pub fn is_widget(&self) -> bool {
match self {
SceneElement::WidgetElement(_) => true,
_ => false,
}
}
}
struct SceneReader {
pixels: Option<Vec<u8>>,
draw_type: DrawType,
}
impl SceneReader {
pub fn new(draw_type: DrawType) -> Self {
Self {
pixels: None,
draw_type,
}
}
fn read_pixel(&self, byte0: usize) -> Option<SceneElement> {
let pixels = self.pixels.as_ref().unwrap();
let a = *pixels.get(byte0 + 3)? as u32;
let r = (*pixels.get(byte0 + 2)? as u32) << 16;
let g = (*pixels.get(byte0 + 1)? as u32) << 8;
let b = (*pixels.get(byte0)?) as u32;
let color = r + g + b;
if a == 0xFF {
None
} else {
match self.draw_type {
DrawType::Grid => Some(SceneElement::Grid(a, color as usize)),
DrawType::Design => Some(SceneElement::DesignElement(a, color)),
DrawType::Phantom => Some(SceneElement::PhantomElement(
utils::phantom_helix_decoder(color),
)),
DrawType::Widget => Some(SceneElement::WidgetElement(color)),
DrawType::Scene => unreachable!(),
}
}
}
}