nightshade-api 0.40.0

Procedural high level API for the nightshade game engine
Documentation
//! Per-entity looks. The first call on an entity gives it its own material,
//! so coloring one cube never recolors another.

use crate::scene::api_material_name;
use nightshade::prelude::*;
use nightshade::render::wgpu::texture_cache::{
    SamplerSettings, TextureUsage, texture_cache_remove_reference,
};

/// Sets the entity's base color as linear RGBA.
pub fn set_color(world: &mut World, entity: Entity, color: [f32; 4]) {
    mutate_material(world, entity, |material| {
        material.base_color = color;
    });
}

/// Sets the entity's metallic and roughness factors, both 0.0 to 1.0.
pub fn set_metallic_roughness(world: &mut World, entity: Entity, metallic: f32, roughness: f32) {
    mutate_material(world, entity, |material| {
        material.metallic = metallic;
        material.roughness = roughness;
    });
}

/// Makes the entity glow with the given RGB color and strength. Pairs well
/// with [`set_bloom`](crate::prelude::set_bloom).
pub fn set_emissive(world: &mut World, entity: Entity, color: [f32; 3], strength: f32) {
    mutate_material(world, entity, |material| {
        material.emissive_factor = color;
        material.emissive_strength = strength;
    });
}

/// Disables lighting on the entity so its color renders as is.
pub fn set_unlit(world: &mut World, entity: Entity, unlit: bool) {
    mutate_material(world, entity, |material| {
        material.unlit = unlit;
    });
}

/// Sets the entity's base color texture by name. The built in procedural
/// textures `"checkerboard"`, `"gradient"`, and `"uv_test"` are always
/// available, and [`load_texture`] registers your own.
pub fn set_texture(world: &mut World, entity: Entity, texture_name: &str) {
    let name = texture_name.to_string();
    mutate_material(world, entity, move |material| {
        material.base_texture = Some(name);
    });
}

/// Registers a texture under `name` from encoded png or jpeg bytes. The
/// texture decodes in the background and stays resident until shutdown.
pub fn load_texture(world: &mut World, name: &str, image_bytes: &[u8]) {
    nightshade::ecs::loading::queue_encoded_texture(
        world,
        name.to_string(),
        image_bytes.to_vec(),
        TextureUsage::Color,
        SamplerSettings::DEFAULT,
    );
    texture_cache_add_reference(&mut world.resources.texture_cache, name);
}

fn owns_material(material_name: &str, entity: Entity) -> bool {
    material_name
        .strip_prefix(crate::runner::MATERIAL_PREFIX)
        .and_then(|suffix| suffix.parse().ok())
        == Some(entity.id)
}

pub(crate) fn owned_color(world: &mut World, entity: Entity) -> Option<[f32; 4]> {
    let material_ref = world.core.get_material_ref(entity).cloned()?;
    if !owns_material(&material_ref.name, entity) {
        let current = registry_entry_by_name(
            &world.resources.assets.material_registry.registry,
            &material_ref.name,
        )
        .map(|material| material.base_color)
        .unwrap_or([1.0, 1.0, 1.0, 1.0]);
        set_color(world, entity, current);
        return Some(current);
    }
    registry_entry_by_name(
        &world.resources.assets.material_registry.registry,
        &material_ref.name,
    )
    .map(|material| material.base_color)
}

fn mutate_material(world: &mut World, entity: Entity, apply: impl FnOnce(&mut Material)) {
    let Some(material_ref) = world.core.get_material_ref(entity).cloned() else {
        return;
    };

    if owns_material(&material_ref.name, entity) {
        let Some(mut material) = registry_entry_by_name(
            &world.resources.assets.material_registry.registry,
            &material_ref.name,
        )
        .cloned() else {
            return;
        };
        let old_textures: Vec<String> = material.texture_names().map(str::to_string).collect();
        apply(&mut material);
        let new_textures: Vec<String> = material.texture_names().map(str::to_string).collect();
        swap_texture_references(world, &old_textures, &new_textures);
        if let Some(existing) = registry_entry_by_name_mut(
            &mut world.resources.assets.material_registry.registry,
            &material_ref.name,
        ) {
            *existing = material;
        }
        world
            .resources
            .mesh_render_state
            .mark_material_dirty(entity);
    } else {
        let mut material = registry_entry_by_name(
            &world.resources.assets.material_registry.registry,
            &material_ref.name,
        )
        .cloned()
        .unwrap_or_default();
        apply(&mut material);
        let textures: Vec<String> = material.texture_names().map(str::to_string).collect();
        for texture in &textures {
            texture_cache_add_reference(&mut world.resources.texture_cache, texture);
        }
        if let Some((index, _)) = registry_lookup_index(
            &world.resources.assets.material_registry.registry,
            &material_ref.name,
        ) {
            registry_remove_reference(
                &mut world.resources.assets.material_registry.registry,
                index,
            );
        }
        register_material(world, entity, api_material_name(entity), material);
    }
}

fn swap_texture_references(world: &mut World, old_textures: &[String], new_textures: &[String]) {
    for texture in old_textures {
        if !new_textures.contains(texture) {
            texture_cache_remove_reference(&mut world.resources.texture_cache, texture);
        }
    }
    for texture in new_textures {
        if !old_textures.contains(texture) {
            texture_cache_add_reference(&mut world.resources.texture_cache, texture);
        }
    }
}