Skip to main content

nightshade_api/
picking.rs

1//! One call interaction: what is under the cursor, what did the user click.
2
3use crate::input::{mouse_clicked, mouse_position};
4use crate::scene::is_reserved;
5use nightshade::ecs::gpu_picking::{GpuPickResult, surface_pick_coords};
6use nightshade::prelude::*;
7use nightshade::render::config::ViewportRect;
8use std::collections::HashSet;
9
10/// The closest entity under the cursor, if any. The api's own scaffolding
11/// (draw pools, cameras, lights) is never returned.
12pub fn entity_under_cursor(world: &World) -> Option<Entity> {
13    pick_entities(world, mouse_position(world), PickingOptions::default())
14        .into_iter()
15        .map(|result| result.entity)
16        .find(|&entity| !is_reserved(world, entity))
17}
18
19/// The entity the user clicked this frame with the left mouse button, if any.
20pub fn clicked_entity(world: &World) -> Option<Entity> {
21    if !mouse_clicked(world, MouseButton::Left) {
22        return None;
23    }
24    entity_under_cursor(world)
25}
26
27/// Where the cursor's ray meets the ground plane at y zero, if it does.
28pub fn cursor_on_ground(world: &World) -> Option<Vec3> {
29    get_ground_position_from_screen(world, mouse_position(world), 0.0)
30}
31
32/// The closest entity under `screen_pos` that is not in `exclude` and is not
33/// api scaffolding. Lets a tool ray-cast past a translucent placement ghost
34/// that would otherwise pick itself.
35pub fn pick_excluding(
36    world: &World,
37    screen_pos: Vec2,
38    exclude: &HashSet<Entity>,
39) -> Option<Entity> {
40    pick_entities(world, screen_pos, PickingOptions::default())
41        .into_iter()
42        .map(|result| result.entity)
43        .find(|entity| !is_reserved(world, *entity) && !exclude.contains(entity))
44}
45
46/// Requests a precise GPU pick at a window-space position, sampling the depth
47/// and entity-id buffers. The position is mapped through the active viewport
48/// rect (or the full window when none is set) and the render scale, so the
49/// request lands on the correct render-target pixel at any render scale.
50/// Nothing is requested when the position falls outside the viewport. The
51/// result is not ready this frame. Read it next frame with
52/// [`take_surface_pick`].
53pub fn request_surface_pick(world: &mut World, screen_pos: Vec2) {
54    let rect = world
55        .res::<nightshade::ecs::window::resources::Window>()
56        .active_viewport_rect
57        .or_else(|| {
58            world
59                .res::<nightshade::ecs::window::resources::Window>()
60                .cached_viewport_size
61                .map(|(width, height)| ViewportRect {
62                    x: 0.0,
63                    y: 0.0,
64                    width: width as f32,
65                    height: height as f32,
66                })
67        });
68    let render_scale = world
69        .res::<nightshade::render::config::RenderSettings>()
70        .render_scale
71        .clamp(0.25, 4.0);
72    let surface_size = rect
73        .map(|rect| {
74            (
75                ((rect.width * render_scale).round() as u32).max(1),
76                ((rect.height * render_scale).round() as u32).max(1),
77            )
78        })
79        .unwrap_or((1, 1));
80    if let Some((pick_x, pick_y)) = surface_pick_coords(screen_pos, rect, surface_size) {
81        world
82            .res_mut::<nightshade::ecs::gpu_picking::GpuPicking>()
83            .request_pick(pick_x, pick_y);
84    }
85}
86
87/// Takes the result of a [`request_surface_pick`], if the renderer has produced
88/// one. Carries the exact world position, surface normal, and the picked
89/// entity's id.
90pub fn take_surface_pick(world: &mut World) -> Option<GpuPickResult> {
91    world
92        .res_mut::<nightshade::ecs::gpu_picking::GpuPicking>()
93        .take_result()
94}