nightshade-api 0.51.0

Procedural high level API for the nightshade game engine
Documentation
//! One call interaction: what is under the cursor, what did the user click.

use crate::input::{mouse_clicked, mouse_position};
use crate::scene::is_reserved;
use nightshade::ecs::gpu_picking::{GpuPickResult, surface_pick_coords};
use nightshade::prelude::*;
use nightshade::render::config::ViewportRect;
use std::collections::HashSet;

/// The closest entity under the cursor, if any. The api's own scaffolding
/// (draw pools, cameras, lights) is never returned.
pub fn entity_under_cursor(world: &World) -> Option<Entity> {
    pick_entities(world, mouse_position(world), PickingOptions::default())
        .into_iter()
        .map(|result| result.entity)
        .find(|&entity| !is_reserved(world, entity))
}

/// The entity the user clicked this frame with the left mouse button, if any.
pub fn clicked_entity(world: &World) -> Option<Entity> {
    if !mouse_clicked(world, MouseButton::Left) {
        return None;
    }
    entity_under_cursor(world)
}

/// Where the cursor's ray meets the ground plane at y zero, if it does.
pub fn cursor_on_ground(world: &World) -> Option<Vec3> {
    get_ground_position_from_screen(world, mouse_position(world), 0.0)
}

/// The closest entity under `screen_pos` that is not in `exclude` and is not
/// api scaffolding. Lets a tool ray-cast past a translucent placement ghost
/// that would otherwise pick itself.
pub fn pick_excluding(
    world: &World,
    screen_pos: Vec2,
    exclude: &HashSet<Entity>,
) -> Option<Entity> {
    pick_entities(world, screen_pos, PickingOptions::default())
        .into_iter()
        .map(|result| result.entity)
        .find(|entity| !is_reserved(world, *entity) && !exclude.contains(entity))
}

/// Requests a precise GPU pick at a window-space position, sampling the depth
/// and entity-id buffers. The position is mapped through the active viewport
/// rect (or the full window when none is set) and the render scale, so the
/// request lands on the correct render-target pixel at any render scale.
/// Nothing is requested when the position falls outside the viewport. The
/// result is not ready this frame. Read it next frame with
/// [`take_surface_pick`].
pub fn request_surface_pick(world: &mut World, screen_pos: Vec2) {
    let rect = world.resources.window.active_viewport_rect.or_else(|| {
        world
            .resources
            .window
            .cached_viewport_size
            .map(|(width, height)| ViewportRect {
                x: 0.0,
                y: 0.0,
                width: width as f32,
                height: height as f32,
            })
    });
    let render_scale = world
        .resources
        .render_settings
        .render_scale
        .clamp(0.25, 4.0);
    let surface_size = rect
        .map(|rect| {
            (
                ((rect.width * render_scale).round() as u32).max(1),
                ((rect.height * render_scale).round() as u32).max(1),
            )
        })
        .unwrap_or((1, 1));
    if let Some((pick_x, pick_y)) = surface_pick_coords(screen_pos, rect, surface_size) {
        world.resources.gpu_picking.request_pick(pick_x, pick_y);
    }
}

/// Takes the result of a [`request_surface_pick`], if the renderer has produced
/// one. Carries the exact world position, surface normal, and the picked
/// entity's id.
pub fn take_surface_pick(world: &mut World) -> Option<GpuPickResult> {
    world.resources.gpu_picking.take_result()
}