nightshade-api 0.38.0

Procedural high level API for the nightshade game engine
Documentation
//! Immediate mode drawing. Everything drawn with `draw_` is visible for
//! exactly one frame, so redraw it every frame you want it on screen. For
//! geometry that should persist, use the `spawn_` functions instead.
//!
//! Backed by pooled instanced meshes and a shared lines entity, so heavy use
//! costs one instance buffer refill per frame, not entity churn.

use crate::runner::{
    DRAW_CUBE_POOL, DRAW_LINES_POOL, DRAW_MATERIAL, DRAW_SPHERE_POOL, lookup_named, register_named,
};
use nightshade::ecs::mesh::components::{create_cube_mesh, create_sphere_mesh};
use nightshade::prelude::nalgebra_glm::Quat;
use nightshade::prelude::*;

/// Draws a cube at `position` with the given size and color for one frame.
pub fn draw_cube(world: &mut World, position: Vec3, scale: Vec3, color: [f32; 4]) {
    push_instance(
        world,
        DRAW_CUBE_POOL,
        InstanceTransform::new(position, Quat::identity(), scale),
        color,
    );
}

/// Draws a sphere at `position` with the given radius and color for one frame.
pub fn draw_sphere(world: &mut World, position: Vec3, radius: f32, color: [f32; 4]) {
    push_instance(
        world,
        DRAW_SPHERE_POOL,
        InstanceTransform::new(
            position,
            Quat::identity(),
            Vec3::new(radius, radius, radius),
        ),
        color,
    );
}

/// Draws a line from `start` to `end` for one frame.
pub fn draw_line(world: &mut World, start: Vec3, end: Vec3, color: [f32; 4]) {
    let Some(entity) = lookup_named(world, DRAW_LINES_POOL) else {
        return;
    };
    if let Some(lines) = world.core.get_lines_mut(entity) {
        lines.push(Line {
            start,
            end,
            color: Vec4::new(color[0], color[1], color[2], color[3]),
        });
    }
}

/// Spawns a retained set of lines that stays until you
/// [`despawn`](crate::prelude::despawn) it.
pub fn spawn_lines(world: &mut World, lines: Vec<Line>) -> Entity {
    let entity = spawn_entities(world, LINES, 1)[0];
    world.core.set_lines(entity, Lines::new(lines));
    entity
}

fn push_instance(
    world: &mut World,
    pool_name: &str,
    transform: InstanceTransform,
    color: [f32; 4],
) {
    let Some(entity) = lookup_named(world, pool_name) else {
        return;
    };
    if let Some(instanced) = world.core.get_instanced_mesh_mut(entity) {
        instanced.add_instance(transform);
        let index = instanced.instance_count() - 1;
        instanced.set_instance_tint(index, color);
    }
}

pub(crate) fn initialize_draw_pools(world: &mut World) {
    ensure_pool_mesh(world, "Cube");
    ensure_pool_mesh(world, "Sphere");

    material_registry_insert(
        &mut world.resources.assets.material_registry,
        DRAW_MATERIAL.to_string(),
        Material::default(),
    );
    if let Some((index, _)) = registry_lookup_index(
        &world.resources.assets.material_registry.registry,
        DRAW_MATERIAL,
    ) {
        registry_add_reference(
            &mut world.resources.assets.material_registry.registry,
            index,
        );
    }

    let cube_pool = spawn_instanced_mesh_with_material(world, "Cube", Vec::new(), DRAW_MATERIAL);
    world
        .core
        .set_name(cube_pool, Name(DRAW_CUBE_POOL.to_string()));
    register_named(world, DRAW_CUBE_POOL, cube_pool);
    let sphere_pool =
        spawn_instanced_mesh_with_material(world, "Sphere", Vec::new(), DRAW_MATERIAL);
    world
        .core
        .set_name(sphere_pool, Name(DRAW_SPHERE_POOL.to_string()));
    register_named(world, DRAW_SPHERE_POOL, sphere_pool);

    let lines_pool = spawn_entities(world, LINES | NAME, 1)[0];
    world
        .core
        .set_name(lines_pool, Name(DRAW_LINES_POOL.to_string()));
    world.core.set_lines(lines_pool, Lines::new(Vec::new()));
    register_named(world, DRAW_LINES_POOL, lines_pool);
}

pub(crate) fn clear_draw_pools(world: &mut World) {
    for pool_name in [DRAW_CUBE_POOL, DRAW_SPHERE_POOL] {
        if let Some(entity) = lookup_named(world, pool_name)
            && let Some(instanced) = world.core.get_instanced_mesh_mut(entity)
            && instanced.instance_count() > 0
        {
            instanced.clear_instances();
        }
    }
    if let Some(entity) = lookup_named(world, DRAW_LINES_POOL)
        && let Some(lines) = world.core.get_lines_mut(entity)
        && !lines.lines.is_empty()
    {
        lines.clear();
    }
}

fn ensure_pool_mesh(world: &mut World, mesh_name: &str) {
    if !world
        .resources
        .assets
        .mesh_cache
        .registry
        .name_to_index
        .contains_key(mesh_name)
    {
        let mesh = match mesh_name {
            "Cube" => create_cube_mesh(),
            _ => create_sphere_mesh(1.0, 16),
        };
        mesh_cache_insert(
            &mut world.resources.assets.mesh_cache,
            mesh_name.to_string(),
            mesh,
        );
    }
    if let Some((index, _)) =
        registry_lookup_index(&world.resources.assets.mesh_cache.registry, mesh_name)
    {
        registry_add_reference(&mut world.resources.assets.mesh_cache.registry, index);
    }
}