use crate::ecs::material::components::MaterialRef;
use crate::ecs::material::resources::{material_registry_insert, material_registry_remove_unused};
use crate::ecs::world::{Entity, NAME, World};
#[cfg(feature = "physics")]
use crate::plugins::physics::resources::{
physics_world_add_collider, physics_world_add_rigid_body,
};
use crate::render::generational_registry::registry_entry_by_name;
use crate::render::mesh_cache::{mesh_cache_insert, mesh_cache_remove_unused};
use crate::render::procedural_meshes::{
create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
create_sphere_mesh, create_subdivided_plane_mesh, create_torus_mesh,
};
use crate::render::wgpu::texture_cache::{
TextureOwner, texture_cache_acquire, texture_cache_release_entity, texture_cache_remove_unused,
};
use crate::prelude::*;
#[derive(Debug, Clone)]
pub enum EcsCommand {
DespawnRecursive {
entity: Entity,
},
ReloadMaterial {
name: String,
material: Box<crate::render::material::Material>,
},
}
#[derive(Debug, Clone)]
pub enum RenderCommand {
UploadUiImageLayer {
layer: u32,
rgba_data: Vec<u8>,
width: u32,
height: u32,
},
LoadHdrSkybox {
hdr_data: Vec<u8>,
},
LoadHdrSkyboxFromPath {
path: std::path::PathBuf,
},
CaptureScreenshot {
path: Option<std::path::PathBuf>,
max_dimension: Option<u32>,
},
ReloadTexture {
name: String,
rgba_data: Vec<u8>,
width: u32,
height: u32,
},
SetColorLut {
data: Vec<u8>,
},
}
#[derive(Default)]
pub struct CommandQueues {
pub ecs: Vec<EcsCommand>,
pub render: Vec<RenderCommand>,
}
pub fn spawn_entities(world: &mut World, core_mask: u64, count: usize) -> Vec<Entity> {
world.ecs.spawn_entities(CORE, core_mask, count)
}
pub fn spawn_entities_ui(world: &mut World, ui_mask: u64, count: usize) -> Vec<Entity> {
world.ecs.spawn_entities(UI, ui_mask, count)
}
pub fn queue_ecs_command(world: &mut World, command: EcsCommand) {
world.resources.commands.ecs.push(command);
}
pub fn queue_render_command(world: &mut World, command: RenderCommand) {
world.resources.commands.render.push(command);
}
pub fn set_cursor_locked(world: &mut World, locked: bool) {
if let Some(window_handle) = &world.resources.window.handle {
if locked {
if window_handle
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
.is_err()
{
let _ = window_handle.set_cursor_grab(winit::window::CursorGrabMode::Confined);
}
} else {
let _ = window_handle.set_cursor_grab(winit::window::CursorGrabMode::None);
}
}
#[cfg(not(target_arch = "wasm32"))]
{
world.resources.window.cursor_locked = locked;
}
}
#[cfg(target_arch = "wasm32")]
pub fn sync_cursor_lock_state(world: &mut World) {
let locked = web_sys::window()
.and_then(|window| window.document())
.and_then(|document| document.pointer_lock_element())
.is_some();
world.resources.window.cursor_locked = locked;
}
pub fn set_cursor_visible(world: &mut World, visible: bool) {
if let Some(window_handle) = &world.resources.window.handle {
window_handle.set_cursor_visible(visible);
}
}
pub fn set_time_scale(world: &mut World, scale: f32) {
world.resources.window.timing.time_speed = scale.max(0.0);
}
pub fn time_scale(world: &World) -> f32 {
world.resources.window.timing.time_speed
}
pub fn pause(world: &mut World) {
world.resources.window.timing.paused = true;
}
pub fn unpause(world: &mut World) {
world.resources.window.timing.paused = false;
}
pub fn set_paused(world: &mut World, paused: bool) {
world.resources.window.timing.paused = paused;
}
pub fn is_paused(world: &World) -> bool {
world.resources.window.timing.paused
}
pub fn register_material(
world: &mut World,
entity: Entity,
name: String,
material: crate::render::material::Material,
) {
material_registry_insert(
&mut world.resources.assets.material_registry,
name.clone(),
material,
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(&name)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
world.set(entity, MaterialRef::new(name));
world
.resources
.mesh_render_state
.mark_entity_added(render_entity(entity));
}
#[cfg(feature = "physics")]
pub fn spawn_physics_body(world: &mut World, entity: Entity) {
let rigid_body_comp = world
.get::<crate::plugins::physics::components::RigidBodyComponent>(entity)
.cloned();
let collider_comp = world
.get::<crate::plugins::physics::components::ColliderComponent>(entity)
.cloned();
if let Some(rigid_body_comp) = rigid_body_comp {
let rapier_body = rigid_body_comp.to_rapier_rigid_body();
let rapier_handle = physics_world_add_rigid_body(&mut world.resources.physics, rapier_body);
if let Some(collider_comp) = collider_comp {
let rapier_collider = collider_comp.to_rapier_collider();
physics_world_add_collider(
&mut world.resources.physics,
rapier_collider,
rapier_handle,
);
}
if let Some(rigid_body_mut) =
world.get_mut::<crate::plugins::physics::components::RigidBodyComponent>(entity)
{
rigid_body_mut.handle =
Some(crate::plugins::physics::types::rigid_body_handle_from_rapier(rapier_handle));
}
world
.resources
.physics
.handle_to_entity
.insert(rapier_handle, entity);
}
}
pub fn process_commands_system(world: &mut World) {
let commands = std::mem::take(&mut world.resources.commands.ecs);
let _span = tracing::info_span!("ecs_commands", count = commands.len()).entered();
for command in commands {
match command {
EcsCommand::DespawnRecursive { entity } => {
despawn_recursive_immediate(world, entity);
}
EcsCommand::ReloadMaterial { name, material } => {
reload_material_command(world, name, *material);
}
}
}
}
fn reload_material_command(
world: &mut World,
name: String,
new_material: crate::render::material::Material,
) {
use crate::ecs::world::MATERIAL_REF;
let new_textures: Vec<String> = new_material.texture_names().map(str::to_string).collect();
crate::ecs::material::resources::material_registry_mutate(
&mut world.resources.assets.material_registry,
&name,
|existing| *existing = new_material,
);
let referencing_entities: Vec<Entity> = world.ecs.worlds[CORE]
.query_entities(MATERIAL_REF)
.filter(|&entity| {
world
.get::<crate::ecs::material::components::MaterialRef>(entity)
.is_some_and(|mat_ref| mat_ref.name == name)
})
.collect();
for entity in referencing_entities {
texture_cache_acquire(
&mut world.resources.texture_cache,
TextureOwner::EntityMaterial(render_entity(entity)),
new_textures.clone(),
);
world
.resources
.mesh_render_state
.mark_material_dirty(render_entity(entity));
}
}
pub fn despawn_entities_with_cache_cleanup(world: &mut World, entities: &[Entity]) {
for &entity in entities {
world.resources.entities.tags.remove(&entity);
if let Some(guid) = world.get::<crate::ecs::primitives::Guid>(entity).copied() {
world.resources.entities.guid_index.remove(&guid.0);
}
if let Some(render_mesh) = world.get::<crate::ecs::mesh::components::RenderMesh>(entity) {
let mesh_name = render_mesh.name.clone();
if let Some(&index) = world
.resources
.assets
.mesh_cache
.registry
.name_to_index
.get(&mesh_name)
{
registry_remove_reference(&mut world.resources.assets.mesh_cache.registry, index);
}
world
.resources
.mesh_render_state
.mark_entity_removed(render_entity(entity));
}
if let Some(instanced_mesh) =
world.get::<crate::ecs::mesh::components::InstancedMesh>(entity)
{
let mesh_name = instanced_mesh.mesh_name.clone();
if let Some(&index) = world
.resources
.assets
.mesh_cache
.registry
.name_to_index
.get(&mesh_name)
{
registry_remove_reference(&mut world.resources.assets.mesh_cache.registry, index);
}
world
.resources
.mesh_render_state
.mark_instanced_meshes_changed();
}
texture_cache_release_entity(&mut world.resources.texture_cache, render_entity(entity));
if let Some(material_ref) =
world.get::<crate::ecs::material::components::MaterialRef>(entity)
{
let material_name = material_ref.name.clone();
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(&material_name)
{
registry_remove_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
}
}
world.despawn_entities(entities);
}
pub fn despawn_recursive_immediate(world: &mut World, entity: Entity) {
crate::ecs::transform::systems::sync_hierarchy_index(world);
let mut targets = world.resources.hierarchy.descendants(entity);
targets.insert(0, entity);
despawn_entities_with_cache_cleanup(world, &targets);
}
pub(crate) fn setup_entity_transforms(
world: &mut World,
entity: Entity,
local_transform: crate::ecs::world::components::LocalTransform,
) {
world.set(entity, local_transform);
world.set(
entity,
crate::ecs::world::components::GlobalTransform::default(),
);
}
pub fn spawn_sun(world: &mut World) -> Entity {
use crate::ecs::world::components;
world.ecs.spawn_with((
components::Name("Sun".to_string()),
components::LocalTransform {
translation: nalgebra_glm::Vec3::new(5.0, 10.0, 5.0),
rotation: nalgebra_glm::quat_angle_axis(
std::f32::consts::FRAC_PI_4,
&nalgebra_glm::Vec3::new(0.0, 1.0, 0.0),
) * nalgebra_glm::quat_angle_axis(
-std::f32::consts::FRAC_PI_6,
&nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
},
components::GlobalTransform::default(),
components::Light {
light_type: components::LightType::Directional,
color: nalgebra_glm::Vec3::new(1.0, 0.95, 0.8),
intensity: 5.0,
range: 100.0,
inner_cone_angle: std::f32::consts::PI / 6.0,
outer_cone_angle: std::f32::consts::PI / 4.0,
cast_shadows: true,
shadow_bias: 0.0005,
shadow_resolution: 0,
shadow_distance: 0.0,
cookie_texture: None,
..Default::default()
},
))
}
pub fn spawn_sun_without_shadows(world: &mut World) -> Entity {
use crate::ecs::world::components;
world.ecs.spawn_with((
components::Name("Sun".to_string()),
components::LocalTransform {
translation: nalgebra_glm::Vec3::new(5.0, 10.0, 5.0),
rotation: nalgebra_glm::quat_angle_axis(
-std::f32::consts::FRAC_PI_4,
&nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
},
components::GlobalTransform::default(),
components::Light {
light_type: components::LightType::Directional,
color: nalgebra_glm::Vec3::new(1.0, 0.95, 0.8),
intensity: 5.0,
range: 100.0,
inner_cone_angle: std::f32::consts::PI / 6.0,
outer_cone_angle: std::f32::consts::PI / 4.0,
cast_shadows: false,
shadow_bias: 0.0,
shadow_resolution: 0,
shadow_distance: 0.0,
cookie_texture: None,
..Default::default()
},
))
}
pub fn spawn_light_entity(world: &mut World, position: nalgebra_glm::Vec3, name: &str) -> Entity {
use crate::ecs::world::components;
use crate::ecs::world::{GLOBAL_TRANSFORM, LIGHT, LOCAL_TRANSFORM, NAME};
let entity = spawn_entities(world, NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LIGHT, 1)[0];
world.set(entity, components::Name(name.to_string()));
setup_entity_transforms(
world,
entity,
components::LocalTransform {
translation: position,
rotation: nalgebra_glm::quat_identity(),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
},
);
entity
}
pub fn spawn_mesh_at(
world: &mut World,
mesh_name: &str,
position: nalgebra_glm::Vec3,
scale: nalgebra_glm::Vec3,
) -> Entity {
use crate::ecs::world::components;
let render_mesh = components::RenderMesh::new(mesh_name);
if !world
.resources
.assets
.mesh_cache
.registry
.name_to_index
.contains_key(&render_mesh.name)
{
let mesh = match mesh_name {
"Cube" => Some(create_cube_mesh()),
"Sphere" => Some(create_sphere_mesh(1.0, 16)),
"Plane" => Some(create_plane_mesh(2.0)),
"SubdividedPlane" => Some(create_subdivided_plane_mesh(2.0, 20)),
"Torus" => Some(create_torus_mesh(1.0, 0.3, 32, 16)),
"Cylinder" => Some(create_cylinder_mesh(0.5, 1.0, 16)),
"Cone" => Some(create_cone_mesh(0.5, 1.0, 16)),
_ => None,
};
if let Some(mesh) = mesh {
mesh_cache_insert(
&mut world.resources.assets.mesh_cache,
mesh_name.to_string(),
mesh,
);
}
}
if let Some(&index) = world
.resources
.assets
.mesh_cache
.registry
.name_to_index
.get(&render_mesh.name)
{
registry_add_reference(&mut world.resources.assets.mesh_cache.registry, index);
}
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get("Default")
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
let entity = world.ecs.spawn_with((
components::Name(mesh_name.to_string()),
components::LocalTransform {
translation: position,
scale,
rotation: nalgebra_glm::Quat::identity(),
},
components::GlobalTransform::default(),
render_mesh,
components::MaterialRef::new("Default"),
components::BoundingVolume::from_mesh_type(mesh_name),
components::CastsShadow,
crate::ecs::primitives::Visibility { visible: true },
));
world
.resources
.mesh_render_state
.mark_entity_added(render_entity(entity));
entity
}
pub fn spawn_cube_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Cube",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_sphere_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Sphere",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_cylinder_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Cylinder",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_torus_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Torus",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_cone_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Cone",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_plane_at(world: &mut World, position: nalgebra_glm::Vec3) -> Entity {
spawn_mesh_at(
world,
"Plane",
position,
nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
)
}
pub fn spawn_3d_text_with_properties(
world: &mut World,
text: &str,
position: nalgebra_glm::Vec3,
properties: crate::ecs::text::components::TextProperties,
) -> Entity {
spawn_3d_text_impl(world, text, position, properties, false)
}
pub fn spawn_3d_billboard_text_with_properties(
world: &mut World,
text: &str,
position: nalgebra_glm::Vec3,
properties: crate::ecs::text::components::TextProperties,
) -> Entity {
spawn_3d_text_impl(world, text, position, properties, true)
}
fn spawn_3d_text_impl(
world: &mut World,
text: &str,
position: nalgebra_glm::Vec3,
properties: crate::ecs::text::components::TextProperties,
billboard: bool,
) -> Entity {
use crate::ecs::world::components;
use crate::ecs::world::{GLOBAL_TRANSFORM, LOCAL_TRANSFORM, NAME, TEXT, VISIBILITY};
let text_index = world.resources.text.cache.add_text(text);
let entity = spawn_entities(
world,
NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | TEXT | VISIBILITY,
1,
)[0];
world.set(entity, components::Name(text.to_string()));
world.set(
entity,
components::LocalTransform {
translation: position,
rotation: nalgebra_glm::Quat::identity(),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
},
);
world.set(entity, components::GlobalTransform::default());
world.set(
entity,
components::Text {
text_index,
properties,
dirty: true,
cached_mesh: None,
billboard,
},
);
world.set(entity, components::Visibility { visible: true });
entity
}
const CLEANUP_INTERVAL_FRAMES: u64 = 300;
pub fn cleanup_unused_resources_system(world: &mut World) {
let _span = tracing::info_span!("cleanup").entered();
world.resources.cleanup.frame_counter += 1;
if world.resources.cleanup.frame_counter >= CLEANUP_INTERVAL_FRAMES {
world.resources.cleanup.frame_counter = 0;
mesh_cache_remove_unused(&mut world.resources.assets.mesh_cache);
material_registry_remove_unused(&mut world.resources.assets.material_registry);
texture_cache_remove_unused(&mut world.resources.texture_cache);
}
}
pub fn set_material_with_textures(
world: &mut World,
entity: Entity,
material: crate::render::material::Material,
) {
let material_ref = world
.get::<crate::ecs::material::components::MaterialRef>(entity)
.cloned();
let texture_names: Vec<String> = material.texture_names().map(str::to_string).collect();
texture_cache_acquire(
&mut world.resources.texture_cache,
TextureOwner::EntityMaterial(render_entity(entity)),
texture_names,
);
if let Some(ref mat_ref) = material_ref {
crate::ecs::material::resources::material_registry_mutate(
&mut world.resources.assets.material_registry,
&mat_ref.name,
|existing_mat| *existing_mat = material,
);
world
.resources
.mesh_render_state
.mark_material_dirty(render_entity(entity));
} else {
let material_name = format!("TexturedMaterial_{}", entity.id);
material_registry_insert(
&mut world.resources.assets.material_registry,
material_name.clone(),
material,
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(&material_name)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
world.set(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
}
}
pub fn load_procedural_textures(world: &mut World) {
let default_sampler = crate::render::texture_data::SamplerSettings::DEFAULT;
let color = crate::render::texture_data::TextureUsage::Color;
let checkerboard = generate_checkerboard_texture();
crate::ecs::loading::queue_decoded_texture(
world,
"checkerboard".to_string(),
checkerboard.0,
checkerboard.1,
checkerboard.2,
color,
default_sampler,
);
let gradient = generate_gradient_texture();
crate::ecs::loading::queue_decoded_texture(
world,
"gradient".to_string(),
gradient.0,
gradient.1,
gradient.2,
color,
default_sampler,
);
let uv_test = generate_uv_test_texture();
crate::ecs::loading::queue_decoded_texture(
world,
"uv_test".to_string(),
uv_test.0,
uv_test.1,
uv_test.2,
color,
default_sampler,
);
for name in ["checkerboard", "gradient", "uv_test"] {
crate::render::wgpu::texture_cache::texture_cache_protect(
&mut world.resources.texture_cache,
name.to_string(),
);
}
}
fn generate_checkerboard_texture() -> (Vec<u8>, u32, u32) {
let width = 256;
let height = 256;
let checker_size = 32;
let mut pixels = Vec::new();
for y in 0..height {
for x in 0..width {
let checker_x = (x / checker_size) % 2;
let checker_y = (y / checker_size) % 2;
let is_white = (checker_x + checker_y) % 2 == 0;
if is_white {
pixels.extend_from_slice(&[255, 255, 255, 255]);
} else {
pixels.extend_from_slice(&[64, 64, 64, 255]);
}
}
}
(pixels, width, height)
}
fn generate_gradient_texture() -> (Vec<u8>, u32, u32) {
let width = 256;
let height = 256;
let mut pixels = Vec::new();
for y in 0..height {
for x in 0..width {
let r = (x * 255 / width) as u8;
let g = (y * 255 / height) as u8;
let b = 128u8;
pixels.extend_from_slice(&[r, g, b, 255]);
}
}
(pixels, width, height)
}
fn generate_uv_test_texture() -> (Vec<u8>, u32, u32) {
let width = 256;
let height = 256;
let mut pixels = Vec::new();
for y in 0..height {
for x in 0..width {
let u = x as f32 / width as f32;
let v = y as f32 / height as f32;
let r = (u * 255.0) as u8;
let g = (v * 255.0) as u8;
let b = ((1.0 - u) * (1.0 - v) * 255.0) as u8;
pixels.extend_from_slice(&[r, g, b, 255]);
}
}
(pixels, width, height)
}
pub fn load_hdr_skybox(world: &mut World, hdr_data: Vec<u8>) {
queue_render_command(world, RenderCommand::LoadHdrSkybox { hdr_data });
}
pub fn load_hdr_skybox_from_path(world: &mut World, path: std::path::PathBuf) {
queue_render_command(world, RenderCommand::LoadHdrSkyboxFromPath { path });
}
#[cfg(not(target_arch = "wasm32"))]
pub fn capture_screenshot(world: &mut World) {
queue_render_command(
world,
RenderCommand::CaptureScreenshot {
path: None,
max_dimension: None,
},
);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn capture_screenshot_to_path(world: &mut World, path: impl Into<std::path::PathBuf>) {
queue_render_command(
world,
RenderCommand::CaptureScreenshot {
path: Some(path.into()),
max_dimension: None,
},
);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn capture_thumbnail_to_path(
world: &mut World,
path: impl Into<std::path::PathBuf>,
max_dimension: u32,
) {
queue_render_command(
world,
RenderCommand::CaptureScreenshot {
path: Some(path.into()),
max_dimension: Some(max_dimension),
},
);
}
pub fn spawn_instanced_mesh(
world: &mut World,
mesh_name: &str,
instances: Vec<crate::ecs::mesh::components::InstanceTransform>,
) -> Entity {
use crate::ecs::world::components;
use crate::ecs::world::{
CASTS_SHADOW, GLOBAL_TRANSFORM, INSTANCED_MESH, LOCAL_TRANSFORM, MATERIAL_REF, NAME,
VISIBILITY,
};
let entity = spawn_entities(
world,
NAME | LOCAL_TRANSFORM
| GLOBAL_TRANSFORM
| INSTANCED_MESH
| MATERIAL_REF
| VISIBILITY
| CASTS_SHADOW,
1,
)[0];
world.set(entity, components::Name(format!("Instanced {}", mesh_name)));
world.set(
entity,
components::LocalTransform {
translation: nalgebra_glm::Vec3::new(0.0, 0.0, 0.0),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
rotation: nalgebra_glm::Quat::identity(),
},
);
world.set(entity, components::GlobalTransform::default());
world.set(
entity,
crate::ecs::mesh::components::InstancedMesh::with_instances(mesh_name, instances),
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get("Default")
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
world.set(entity, components::MaterialRef::new("Default"));
world.set(entity, components::Visibility { visible: true });
world.set(entity, components::CastsShadow);
entity
}
pub fn spawn_instanced_mesh_with_material(
world: &mut World,
mesh_name: &str,
instances: Vec<crate::ecs::mesh::components::InstanceTransform>,
material_name: &str,
) -> Entity {
use crate::ecs::world::components;
use crate::ecs::world::{
CASTS_SHADOW, GLOBAL_TRANSFORM, INSTANCED_MESH, LOCAL_TRANSFORM, MATERIAL_REF, NAME,
VISIBILITY,
};
let entity = spawn_entities(
world,
NAME | LOCAL_TRANSFORM
| GLOBAL_TRANSFORM
| INSTANCED_MESH
| MATERIAL_REF
| VISIBILITY
| CASTS_SHADOW,
1,
)[0];
world.set(entity, components::Name(format!("Instanced {}", mesh_name)));
world.set(
entity,
components::LocalTransform {
translation: nalgebra_glm::Vec3::new(0.0, 0.0, 0.0),
scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
rotation: nalgebra_glm::Quat::identity(),
},
);
world.set(entity, components::GlobalTransform::default());
world.set(
entity,
crate::ecs::mesh::components::InstancedMesh::with_instances(mesh_name, instances),
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(material_name)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
let texture_names: Vec<String> = registry_entry_by_name(
&world.resources.assets.material_registry.registry,
material_name,
)
.map(|mat| mat.texture_names().map(str::to_string).collect())
.unwrap_or_default();
texture_cache_acquire(
&mut world.resources.texture_cache,
TextureOwner::EntityMaterial(render_entity(entity)),
texture_names,
);
world.set(entity, components::MaterialRef::new(material_name));
world.set(entity, components::Visibility { visible: true });
world.set(entity, components::CastsShadow);
entity
}
pub fn find_entity_by_name(world: &World, name: &str) -> Option<Entity> {
world.ecs.worlds[CORE].query_entities(NAME).find(|&entity| {
world
.get::<crate::ecs::primitives::Name>(entity)
.is_some_and(|n| n.0 == name)
})
}
pub fn spawn_material(
world: &mut World,
entity: Entity,
name: String,
material: crate::render::material::Material,
) {
material_registry_insert(
&mut world.resources.assets.material_registry,
name.clone(),
material,
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(&name)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
world.set(entity, MaterialRef::new(name));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn process_commands_system_drains_ecs_and_leaves_render() {
let mut world = World::default();
let entity = world.spawn_count(1)[0];
queue_ecs_command(&mut world, EcsCommand::DespawnRecursive { entity });
queue_render_command(
&mut world,
RenderCommand::LoadHdrSkybox {
hdr_data: vec![0u8; 4],
},
);
process_commands_system(&mut world);
assert!(
world.resources.commands.ecs.is_empty(),
"process_commands_system should drain the ecs queue"
);
assert_eq!(
world.resources.commands.render.len(),
1,
"process_commands_system must not touch the render queue"
);
assert!(matches!(
world.resources.commands.render[0],
RenderCommand::LoadHdrSkybox { .. }
));
}
}