gloss_renderer/
selector.rs

1#[cfg(feature = "selector")]
2use crate::{
3    components::{CamController, Name, VisOutline},
4    plugin_manager::{plugins::InternalPlugin, GpuSystem, RunnerState},
5    scene::Scene,
6    viewer::GpuResources,
7};
8
9#[cfg(feature = "selector")]
10use crossbeam_channel;
11#[cfg(all(target_arch = "wasm32", feature = "selector"))]
12use wasm_bindgen_futures;
13
14pub struct Selector {
15    pub current_selected: Option<String>,
16}
17
18/// Resource that holds the receiver for pixel data downloaded from ent id pass.
19/// The data stored here is the entity id of the selected entity.
20#[cfg(feature = "selector")]
21pub struct SelectorPixelReceiver(pub crossbeam_channel::Receiver<u8>);
22
23impl Default for Selector {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl Selector {
30    pub fn new() -> Self {
31        Self { current_selected: None }
32    }
33}
34
35#[derive(Clone)]
36pub struct SelectorPlugin {
37    pub autorun: bool,
38}
39
40impl SelectorPlugin {
41    pub fn new(autorun: bool) -> Self {
42        Self { autorun }
43    }
44}
45
46#[cfg(feature = "selector")]
47impl InternalPlugin for SelectorPlugin {
48    fn autorun(&self) -> bool {
49        self.autorun
50    }
51    fn gpu_systems(&self) -> Vec<GpuSystem> {
52        vec![
53            GpuSystem::new(add_selector_resource).with_name("selector_add_resource_system"),
54            GpuSystem::new(queue_pixel_download).with_name("selector_queue_download_system"),
55            GpuSystem::new(redraw_if_queued).with_name("selector_redraw_if_queued_system"),
56            GpuSystem::new(process_received_pixels).with_name("selector_process_pixels_system"),
57        ]
58    }
59}
60
61// we add this resource as long as the feature is enabled
62// we can after choose to remove it if we want to disable click to select
63#[cfg(feature = "selector")]
64pub fn add_selector_resource(scene: &mut Scene, runner: &mut RunnerState, _gpu_res: &GpuResources) {
65    if runner.is_first_time() {
66        scene.add_resource(Selector::default());
67    }
68}
69
70/// This system redraws if the `SelectorPixelReceiver` resource is present.
71#[cfg(feature = "selector")]
72pub fn redraw_if_queued(scene: &mut Scene, runner: &mut RunnerState, _gpu_res: &GpuResources) {
73    if scene.has_resource::<SelectorPixelReceiver>() {
74        runner.request_redraw();
75    }
76}
77
78/// This system listens to `SelectorPixelReceiver` resource and handles the pixel data.
79#[cfg(feature = "selector")]
80pub fn process_received_pixels(scene: &mut Scene, _runner: &mut RunnerState, _gpu_res: &GpuResources) {
81    // Try to receive pixel data
82    let pixel_data = scene
83        .get_resource::<&SelectorPixelReceiver>()
84        .ok()
85        .and_then(|receiver| receiver.0.try_recv().ok());
86
87    // Early return if no data was received
88    if let Some(pixel_data) = pixel_data {
89        let entity_id = pixel_data;
90
91        // Switch off selection for previous entity
92        if let Ok(mut selector) = scene.get_resource::<&mut Selector>() {
93            if let Some(current_selected) = &selector.current_selected {
94                if let Some(prev_entity) = scene.get_entity_with_name(current_selected) {
95                    if let Ok(mut vis_outline) = scene.world.get::<&mut VisOutline>(prev_entity) {
96                        vis_outline.show_outline = false;
97                    }
98                }
99            }
100            selector.current_selected = None;
101        }
102
103        // Process the new selection
104        if entity_id != 0 {
105            let entity_ref = scene.find_entity_with_id(entity_id);
106
107            if let Some(e_ref) = entity_ref {
108                let name = e_ref.get::<&Name>().expect("The entity has no name").0.clone();
109
110                if let Some(mut vis_outline) = e_ref.get::<&mut VisOutline>() {
111                    vis_outline.show_outline = true;
112                }
113
114                scene.add_resource(Selector {
115                    current_selected: Some(name.clone()),
116                });
117            }
118        }
119
120        let _ = scene.remove_resource::<SelectorPixelReceiver>();
121    }
122}
123
124/// This system detects clicks and queues up the download of pixels.
125#[cfg(feature = "selector")]
126pub fn queue_pixel_download(scene: &mut Scene, runner: &mut RunnerState, gpu_res: &GpuResources) {
127    // Early return if LMB is not clicked or if selector resource is not present
128    if !scene.get_current_cam().unwrap().is_click(scene) || !scene.has_resource::<Selector>() {
129        return;
130    }
131
132    #[allow(clippy::cast_possible_truncation)]
133    #[allow(clippy::cast_sign_loss)]
134    let cursor_pos = scene
135        .get_current_cam()
136        .and_then(|camera| scene.get_comp::<&CamController>(&camera.entity).ok())
137        .and_then(|cam_control| cam_control.cursor_position)
138        .map(|pos| (pos.x as u32, pos.y as u32));
139
140    let Some((x, y)) = cursor_pos else { return };
141
142    let gpu = &gpu_res.gpu;
143
144    // Get the entity id texture from the renderer
145    let entity_id_texture = gpu_res.renderer.entity_id_buffer();
146    let scaled_x = x / entity_id_texture.tex_params.scale_factor;
147    let scaled_y = y / entity_id_texture.tex_params.scale_factor;
148
149    let (sender, receiver) = crossbeam_channel::unbounded();
150
151    // We only need the pixel at selection so we dont really need to download the whole thing
152    #[cfg(not(target_arch = "wasm32"))]
153    {
154        let single_pixel_img =
155            pollster::block_on(entity_id_texture.download_pixel_to_cpu(gpu.device(), gpu.queue(), wgpu::TextureAspect::All, scaled_x, scaled_y));
156        // We send only the first byte of the pixel data, which is the entity id
157        let _ = sender.send(single_pixel_img.as_bytes().to_vec()[0]);
158    }
159
160    #[cfg(target_arch = "wasm32")]
161    {
162        let texture_clone = entity_id_texture.clone();
163        let device = gpu.device().clone();
164        let queue = gpu.queue().clone();
165
166        wasm_bindgen_futures::spawn_local(async move {
167            let pixel_data = texture_clone
168                .download_pixel_to_cpu(&device, &queue, wgpu::TextureAspect::All, scaled_x, scaled_y)
169                .await;
170            // We send only the first byte of the pixel data, which is the entity id
171            let _ = sender.send(pixel_data.as_bytes().to_vec()[0]);
172        });
173    }
174
175    scene.add_resource(SelectorPixelReceiver(receiver));
176    runner.request_redraw();
177}