nightshade-api 0.43.0

Procedural high level API for the nightshade game engine
Documentation
//! Camera presets. Each call replaces the previous api camera, makes the new
//! one active, and the right controller runs automatically every frame.

#[cfg(feature = "physics")]
use crate::runner::{CAMERA_FIRST_PERSON, PLAYER_NAME, lookup_named, register_named};
use crate::runner::{CAMERA_FIXED, CAMERA_FLY, CAMERA_NAME_PREFIX, CAMERA_ORBIT};
#[cfg(feature = "physics")]
use nightshade::ecs::physics::commands::spawn_first_person_player;
use nightshade::prelude::*;

/// An orbit camera looking at `focus` from `radius` away. Drag to orbit,
/// scroll to zoom. This is the default camera every program starts with.
pub fn orbit_camera(world: &mut World, focus: Vec3, radius: f32) -> Entity {
    despawn_api_camera(world);
    let camera = spawn_pan_orbit_camera(world, focus, radius, 0.6, 0.4, CAMERA_ORBIT.to_string());
    activate_camera(world, camera);
    camera
}

/// A free fly camera at `position`. WASD to move, right drag to look.
pub fn fly_camera(world: &mut World, position: Vec3) -> Entity {
    despawn_api_camera(world);
    let camera = spawn_camera(world, position, CAMERA_FLY.to_string());
    activate_camera(world, camera);
    camera
}

/// A walking first person player at `position` with mouse look, WASD, sprint,
/// and jump. The cursor locks to the window. Returns the player entity.
/// `position` is the center of the player capsule, which is two units tall,
/// so spawn a little above the ground and let gravity settle it. Requires the
/// `physics` feature and a floor to stand on.
#[cfg(feature = "physics")]
pub fn first_person(world: &mut World, position: Vec3) -> Entity {
    despawn_api_camera(world);
    let (player, camera) = spawn_first_person_player(world, position);
    world.core.set_name(player, Name(PLAYER_NAME.to_string()));
    register_named(world, PLAYER_NAME, player);
    world
        .core
        .set_name(camera, Name(CAMERA_FIRST_PERSON.to_string()));
    activate_camera(world, camera);
    set_cursor_locked(world, true);
    set_cursor_visible(world, false);
    player
}

/// A stationary camera at `eye` looking at `target`. No controller runs, so
/// the view only changes when you move it yourself with [`look_at`].
pub fn fixed_camera(world: &mut World, eye: Vec3, target: Vec3) -> Entity {
    despawn_api_camera(world);
    let camera = spawn_camera(world, eye, CAMERA_FIXED.to_string());
    if let Some(transform) = mutate_local_transform(world, camera) {
        transform.rotation = camera_look_rotation(eye, target, 0.0);
    }
    activate_camera(world, camera);
    camera
}

/// Sets the first person player's walking speed in units per second. Sprint
/// scales from this.
#[cfg(feature = "physics")]
#[inline]
pub fn set_player_speed(world: &mut World, player: Entity, speed: f32) {
    if let Some(controller) = world.core.get_character_controller_mut(player) {
        controller.max_speed = speed;
    }
}

/// Sets the upward impulse of the first person player's jump.
#[cfg(feature = "physics")]
#[inline]
pub fn set_player_jump(world: &mut World, player: Entity, impulse: f32) {
    if let Some(controller) = world.core.get_character_controller_mut(player) {
        controller.jump_impulse = impulse;
    }
}

/// Moves the active camera to `eye` and points it at `target`.
pub fn look_at(world: &mut World, eye: Vec3, target: Vec3) {
    let Some(camera) = world.resources.active_camera else {
        return;
    };
    if let Some(transform) = mutate_local_transform(world, camera) {
        transform.translation = eye;
        transform.rotation = camera_look_rotation(eye, target, 0.0);
    }
}

/// Glides the orbit camera's focus toward `focus`. Use it as a follow camera
/// by calling it every frame with a moving target.
pub fn set_orbit_focus(world: &mut World, focus: Vec3) {
    let Some(camera) = world.resources.active_camera else {
        return;
    };
    if let Some(orbit) = world.core.get_pan_orbit_camera_mut(camera) {
        orbit.target_focus = focus;
    }
}

/// The active camera's position in world space.
pub fn camera_position(world: &World) -> Vec3 {
    world
        .resources
        .active_camera
        .map(|camera| crate::placement::position(world, camera))
        .unwrap_or_else(Vec3::zeros)
}

/// The direction the active camera is looking, normalized.
pub fn camera_forward(world: &World) -> Vec3 {
    let Some(camera) = world.resources.active_camera else {
        return Vec3::new(0.0, 0.0, -1.0);
    };
    let matrix = crate::placement::world_matrix(world, camera);
    nalgebra_glm::vec3(-matrix[(0, 2)], -matrix[(1, 2)], -matrix[(2, 2)]).normalize()
}

fn despawn_api_camera(world: &mut World) {
    #[cfg(feature = "physics")]
    if let Some(player) = lookup_named(world, PLAYER_NAME) {
        despawn_recursive_immediate(world, player);
        world.resources.entities.names.remove(PLAYER_NAME);
        set_cursor_locked(world, false);
        set_cursor_visible(world, true);
    }
    let api_camera = world.resources.active_camera.filter(|&camera| {
        world
            .core
            .get_name(camera)
            .is_some_and(|name| name.0.starts_with(CAMERA_NAME_PREFIX))
    });
    if let Some(camera) = api_camera {
        despawn_recursive_immediate(world, camera);
    }
    world.resources.active_camera = None;
}

fn activate_camera(world: &mut World, camera: Entity) {
    world.resources.active_camera = Some(camera);
    #[cfg(feature = "audio")]
    {
        world.core.add_components(camera, AUDIO_LISTENER);
        world.core.set_audio_listener(camera, AudioListener);
    }
}