nightshade-api 0.45.0

Procedural high level API for the nightshade game engine
Documentation
//! Fire and forget animation. Each call captures the current value, eases
//! toward the target over the duration, and cleans up after itself. A new
//! animation on the same property replaces the one in flight.

use nightshade::prelude::*;

/// Glides the entity to `to` in its parent's space over `seconds`.
pub fn animate_position(
    world: &mut World,
    entity: Entity,
    to: Vec3,
    seconds: f32,
    easing: EasingFunction,
) {
    let Some(from) = world
        .core
        .get_local_transform(entity)
        .map(|transform| transform.translation)
    else {
        return;
    };
    world.resources.tweens.active.retain(|tween| {
        !(tween.entity == entity && matches!(tween.target, TweenTarget::Position { .. }))
    });
    world.resources.tweens.active.push(Tween {
        entity,
        target: TweenTarget::Position { from, to },
        duration_seconds: seconds,
        elapsed_seconds: 0.0,
        easing,
    });
}

/// Grows or shrinks the entity to `to` over `seconds`.
pub fn animate_scale(
    world: &mut World,
    entity: Entity,
    to: Vec3,
    seconds: f32,
    easing: EasingFunction,
) {
    let Some(from) = world
        .core
        .get_local_transform(entity)
        .map(|transform| transform.scale)
    else {
        return;
    };
    world.resources.tweens.active.retain(|tween| {
        !(tween.entity == entity && matches!(tween.target, TweenTarget::Scale { .. }))
    });
    world.resources.tweens.active.push(Tween {
        entity,
        target: TweenTarget::Scale { from, to },
        duration_seconds: seconds,
        elapsed_seconds: 0.0,
        easing,
    });
}

/// Fades the entity's base color to `to` over `seconds`.
pub fn animate_color(
    world: &mut World,
    entity: Entity,
    to: [f32; 4],
    seconds: f32,
    easing: EasingFunction,
) {
    let Some(from) = crate::appearance::owned_color(world, entity) else {
        return;
    };
    world.resources.tweens.active.retain(|tween| {
        !(tween.entity == entity && matches!(tween.target, TweenTarget::Color { .. }))
    });
    world.resources.tweens.active.push(Tween {
        entity,
        target: TweenTarget::Color { from, to },
        duration_seconds: seconds,
        elapsed_seconds: 0.0,
        easing,
    });
}

/// Kicks the active camera with a jitter that decays to nothing over
/// `seconds`. Strength is in world units, around 0.05 for a rumble and 0.3
/// for an explosion.
pub fn shake_camera(world: &mut World, strength: f32, seconds: f32) {
    let shake = &mut world.resources.tweens.shake;
    shake.strength = strength;
    shake.duration_seconds = seconds;
    shake.remaining_seconds = seconds;
}

/// Bends a two-bone chain so the `tip` bone reaches `target`. Pass the three
/// bone entities root to tip (a leg's hip, knee, foot) and an optional `pole`
/// world point that biases the bend direction (where the knee points). Call it
/// each frame after the skeleton has been posed, so the reach layers on top of
/// the animation. Foot planting, hand placement, reaching.
pub fn reach_to(
    world: &mut World,
    root: Entity,
    mid: Entity,
    tip: Entity,
    target: Vec3,
    pole: Option<Vec3>,
) {
    solve_two_bone_ik(world, root, mid, tip, target, pole);
}

/// Aims a single bone so its local `forward` axis points at `target` in world
/// space, leaving the rest of the pose alone. Heads, eyes, turrets.
pub fn aim_at(world: &mut World, bone: Entity, target: Vec3, forward: Vec3) {
    look_at_bone(world, bone, target, forward);
}