use crate::ecs::world::{CORE, RENDER, World};
use crate::render::render_world;
use std::collections::{HashMap, HashSet};
fn write_object_render(world: &mut World, entity: crate::ecs::world::Entity) {
let state = dynamic_object_state(world, entity);
world.set(entity, render_world::Transform(state.transform));
world.set(entity, render_world::Visible(state.visible != 0));
world.set(entity, render_world::MorphWeights(state.morph_weights));
world.set(entity, render_world::CullingMask(state.culling_mask));
world.set(entity, render_world::Layer(state.render_layer));
set_shadow_tag(world, entity);
}
fn set_shadow_tag(world: &mut World, entity: crate::ecs::world::Entity) {
let casts =
world.ecs.worlds[CORE].entity_has_components(entity, crate::ecs::world::CASTS_SHADOW);
let render = &mut world.ecs.worlds[RENDER];
if casts {
if !render.has_tag_type::<render_world::ShadowCaster>(entity) {
render.add_tag_type::<render_world::ShadowCaster>(entity);
}
} else {
render.remove_tag_type::<render_world::ShadowCaster>(entity);
}
}
fn clear_render_row(world: &mut World, entity: crate::ecs::world::Entity) {
let render = &mut world.ecs.worlds[RENDER];
if let Some(mask) = render.component_mask(entity) {
render.remove_components(entity, mask);
}
render.remove_tag_type::<render_world::ShadowCaster>(entity);
}
fn clear_render_domain(world: &mut World, component: u64) {
let entities: Vec<crate::ecs::world::Entity> =
world.ecs.worlds[RENDER].query_entities(component).collect();
for entity in entities {
clear_render_row(world, entity);
}
}
fn clear_render_component(world: &mut World, component: u64) {
let render = &mut world.ecs.worlds[RENDER];
let entities: Vec<crate::ecs::world::Entity> = render.query_entities(component).collect();
for entity in entities {
render.remove_components(entity, component);
}
}
#[derive(Default)]
pub struct RenderSceneSync {
pub initialized: bool,
pub structural_cursor: u64,
pub data_cursor: u32,
pub materials_dirty: bool,
pub materials_initialized: bool,
pub materials_version_seen: u64,
pub materials_names_version_seen: u64,
pub animation_initialized: bool,
pub animation_cursor: u32,
pub animation_skinned_generation_seen: u64,
pub animation_player_clips: HashMap<crate::ecs::world::Entity, (i64, i64)>,
pub animation_player_clips_scratch: HashMap<crate::ecs::world::Entity, (i64, i64)>,
#[cfg(feature = "meshlet")]
pub meshlet_materials_version_seen: (u64, u64),
}
#[derive(Default)]
struct SceneDirt {
full: bool,
object_refresh: HashSet<crate::ecs::world::Entity>,
instanced_refresh: HashSet<crate::ecs::world::Entity>,
mesh_name_refresh: HashSet<crate::ecs::world::Entity>,
bounds_refresh: HashSet<crate::ecs::world::Entity>,
object_state_dirty: Vec<crate::ecs::world::Entity>,
skinned_objects: bool,
entity_lists: bool,
lights: bool,
decals: bool,
emitters: bool,
water: bool,
cloth: bool,
texts: bool,
#[cfg(feature = "meshlet")]
meshlet_refresh: HashSet<crate::ecs::world::Entity>,
}
const OBJECT_STRUCTURAL_MASK: u64 = crate::ecs::world::RENDER_MESH
| crate::ecs::world::INSTANCED_MESH
| crate::ecs::world::SKIN
| crate::ecs::world::VISIBILITY
| crate::ecs::world::MORPH_WEIGHTS
| crate::ecs::world::RENDER_LAYER
| crate::ecs::world::CULLING_MASK
| crate::ecs::world::CASTS_SHADOW;
fn collect_scene_dirt(world: &mut World) -> SceneDirt {
let mut dirt = SceneDirt::default();
if !world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.initialized
{
dirt.full = true;
}
let structural_cursor = world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.structural_cursor;
let changes: Vec<nightshade_ecs::StructuralChange> = world.ecs.worlds[CORE]
.structural_changes_since(structural_cursor)
.to_vec();
if let Some(first) = changes.first()
&& world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.initialized
&& first.sequence
!= world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.structural_cursor
+ 1
{
dirt.full = true;
}
if let Some(last) = changes.last() {
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.structural_cursor = last.sequence;
world.ecs.worlds[CORE].trim_structural_log(last.sequence);
}
for change in &changes {
let mask = change.mask;
let entity = change.entity;
if mask & (crate::ecs::world::RENDER_MESH | crate::ecs::world::INSTANCED_MESH) != 0 {
dirt.object_refresh.insert(entity);
dirt.mesh_name_refresh.insert(entity);
dirt.instanced_refresh.insert(entity);
dirt.entity_lists = true;
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.materials_dirty = true;
}
if mask & OBJECT_STRUCTURAL_MASK != 0 {
dirt.object_refresh.insert(entity);
dirt.instanced_refresh.insert(entity);
if mask & (crate::ecs::world::SKIN | crate::ecs::world::CULLING_MASK) != 0 {
dirt.entity_lists = true;
}
if mask & crate::ecs::world::SKIN != 0 {
dirt.skinned_objects = true;
}
}
if mask & crate::ecs::world::CASTS_SHADOW != 0 {
dirt.entity_lists = true;
}
if mask & crate::ecs::world::GLOBAL_TRANSFORM != 0
&& let Some(entity_mask) = world.ecs.worlds[CORE].component_mask(entity)
{
if entity_mask & (crate::ecs::world::RENDER_MESH | crate::ecs::world::INSTANCED_MESH)
!= 0
{
dirt.object_refresh.insert(entity);
dirt.instanced_refresh.insert(entity);
dirt.entity_lists = true;
}
if entity_mask & crate::ecs::world::SKIN != 0 {
dirt.entity_lists = true;
dirt.skinned_objects = true;
}
dirt.lights |= entity_mask & crate::ecs::world::LIGHT != 0;
dirt.decals |= entity_mask & crate::ecs::world::DECAL != 0;
dirt.water |= entity_mask & crate::ecs::world::WATER != 0;
dirt.cloth |= entity_mask & crate::ecs::world::CLOTH != 0;
dirt.texts |= entity_mask & crate::ecs::world::TEXT != 0;
}
if mask & crate::ecs::world::BOUNDING_VOLUME != 0 {
dirt.bounds_refresh.insert(entity);
}
if mask & crate::ecs::world::LIGHT != 0 {
dirt.lights = true;
}
if mask & crate::ecs::world::DECAL != 0 {
dirt.decals = true;
}
if mask & crate::ecs::world::PARTICLE_EMITTER != 0 {
dirt.emitters = true;
}
if mask & crate::ecs::world::WATER != 0 {
dirt.water = true;
}
if mask & crate::ecs::world::CLOTH != 0 {
dirt.cloth = true;
}
if mask
& (crate::ecs::world::TEXT
| crate::ecs::world::TEXT_CHARACTER_COLORS
| crate::ecs::world::VISIBILITY)
!= 0
{
dirt.texts = true;
}
if mask & crate::ecs::world::MATERIAL_REF != 0 {
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.materials_dirty = true;
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.mark_material_dirty(entity);
}
}
let since = world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.data_cursor;
let visibility_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::VISIBILITY, since)
.collect();
let morph_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::MORPH_WEIGHTS, since)
.collect();
let layer_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::RENDER_LAYER, since)
.collect();
let culling_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::CULLING_MASK, since)
.collect();
let mesh_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::RENDER_MESH, since)
.collect();
let instanced_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::INSTANCED_MESH, since)
.collect();
let bounds_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::BOUNDING_VOLUME, since)
.collect();
dirt.lights |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::LIGHT, since)
.next()
.is_some();
dirt.decals |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::DECAL, since)
.next()
.is_some();
dirt.emitters |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::PARTICLE_EMITTER, since)
.next()
.is_some();
dirt.water |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::WATER, since)
.next()
.is_some();
dirt.cloth |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::CLOTH, since)
.next()
.is_some();
dirt.texts |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::TEXT, since)
.next()
.is_some();
dirt.texts |= world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::TEXT_CHARACTER_COLORS, since)
.next()
.is_some();
let material_ref_changed: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities_changed_since(crate::ecs::world::MATERIAL_REF, since)
.collect();
if !material_ref_changed.is_empty() {
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.materials_dirty = true;
for entity in material_ref_changed {
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.mark_material_dirty(entity);
}
}
#[cfg(feature = "meshlet")]
{
for change in &changes {
if change.mask & crate::ecs::world::MESHLET_MESH != 0 {
dirt.meshlet_refresh.insert(change.entity);
}
}
for entity in world.ecs.worlds[CORE].query_entities_changed_since(
crate::ecs::world::MESHLET_MESH | crate::ecs::world::GLOBAL_TRANSFORM,
since,
) {
dirt.meshlet_refresh.insert(entity);
}
for entity in world.ecs.worlds[CORE].query_entities_changed_since(
crate::ecs::world::MESHLET_MESH | crate::ecs::world::MATERIAL_REF,
since,
) {
dirt.meshlet_refresh.insert(entity);
}
}
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.data_cursor = world.ecs.worlds[CORE].current_tick();
world.ecs.worlds[CORE].increment_tick();
for entity in visibility_changed {
dirt.object_refresh.insert(entity);
dirt.instanced_refresh.insert(entity);
dirt.object_state_dirty.push(entity);
let mask = world.ecs.worlds[CORE].component_mask(entity).unwrap_or(0);
dirt.skinned_objects |= mask & crate::ecs::world::SKIN != 0;
dirt.lights |= mask & crate::ecs::world::LIGHT != 0;
dirt.decals |= mask & crate::ecs::world::DECAL != 0;
dirt.water |= mask & crate::ecs::world::WATER != 0;
dirt.texts |= mask & crate::ecs::world::TEXT != 0;
}
for entity in morph_changed {
dirt.object_refresh.insert(entity);
dirt.object_state_dirty.push(entity);
dirt.skinned_objects |= world.ecs.worlds[CORE].component_mask(entity).unwrap_or(0)
& crate::ecs::world::SKIN
!= 0;
}
for entity in layer_changed {
dirt.object_refresh.insert(entity);
dirt.instanced_refresh.insert(entity);
}
for entity in culling_changed {
dirt.object_refresh.insert(entity);
}
for entity in mesh_changed {
dirt.object_refresh.insert(entity);
dirt.mesh_name_refresh.insert(entity);
dirt.skinned_objects |= world.ecs.worlds[CORE].component_mask(entity).unwrap_or(0)
& crate::ecs::world::SKIN
!= 0;
}
for entity in instanced_changed {
dirt.instanced_refresh.insert(entity);
}
for entity in bounds_changed {
dirt.bounds_refresh.insert(entity);
dirt.skinned_objects |= world.ecs.worlds[CORE].component_mask(entity).unwrap_or(0)
& crate::ecs::world::SKIN
!= 0;
}
for &entity in &world
.res::<crate::render::mesh_state::MeshRenderState>()
.inner
.transform_dirty
{
let mask = world.ecs.worlds[CORE].component_mask(entity).unwrap_or(0);
if mask & crate::ecs::world::RENDER_MESH != 0 {
dirt.object_refresh.insert(entity);
}
if mask & crate::ecs::world::INSTANCED_MESH != 0 {
dirt.instanced_refresh.insert(entity);
}
if mask & crate::ecs::world::SKIN != 0 {
let previous_flipped = world
.res::<crate::render::config::RendererState>()
.render_skinned_dynamic_state
.get(&entity)
.map(|state| {
nalgebra_glm::determinant(&nalgebra_glm::mat4_to_mat3(&state.transform)) < 0.0
});
let current_flipped = world
.get::<crate::ecs::transform::components::GlobalTransform>(entity)
.map(|global| {
nalgebra_glm::determinant(&nalgebra_glm::mat4_to_mat3(&global.0)) < 0.0
});
dirt.skinned_objects |= previous_flipped != current_flipped;
}
dirt.lights |= mask & crate::ecs::world::LIGHT != 0;
dirt.decals |= mask & crate::ecs::world::DECAL != 0;
dirt.water |= mask & crate::ecs::world::WATER != 0;
dirt.cloth |= mask & crate::ecs::world::CLOTH != 0;
dirt.texts |= mask & crate::ecs::world::TEXT != 0;
}
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.initialized = true;
dirt
}
fn dynamic_object_state(
world: &World,
entity: crate::ecs::world::Entity,
) -> crate::render::config::DynamicRenderState {
let visible = u32::from(
world
.get::<crate::ecs::primitives::Visibility>(entity)
.is_none_or(|v| v.visible),
);
let mut morph_weights = [0.0f32; 8];
if let Some(weights) = world.get::<crate::ecs::morph::components::MorphWeights>(entity) {
for (index, weight) in weights.weights.iter().take(8).enumerate() {
morph_weights[index] = *weight;
}
}
let transform = world
.get::<crate::ecs::transform::components::GlobalTransform>(entity)
.map(|global| global.0)
.unwrap_or_else(nalgebra_glm::Mat4::identity);
let culling_mask = world
.get::<crate::ecs::primitives::CullingMask>(entity)
.copied()
.unwrap_or_default()
.0;
let render_layer_value = world
.get::<crate::render::render_layer::RenderLayer>(entity)
.map(|layer| layer.0)
.unwrap_or(crate::render::render_layer::RenderLayer::WORLD);
let is_overlay =
u32::from(render_layer_value == crate::render::render_layer::RenderLayer::OVERLAY);
crate::render::config::DynamicRenderState {
visible,
morph_weights,
transform,
culling_mask,
is_overlay,
render_layer: render_layer_value,
}
}
fn refill_instanced_object_data(
world: &World,
entity: crate::ecs::world::Entity,
data: &mut crate::render::config::InstancedObjectData,
) -> bool {
let Some(instanced_mesh) = world.get::<crate::ecs::mesh::components::InstancedMesh>(entity)
else {
return false;
};
let matrices = world.res::<crate::ecs::mesh::components::InstancedMeshMatrices>();
let model_matrices = matrices.model_matrices(entity);
data.world_models.clear();
data.world_models
.extend(model_matrices.iter().map(|matrix| matrix.model));
data.world_normals.clear();
data.world_normals
.extend(model_matrices.iter().map(|matrix| matrix.normal_matrix));
data.local_matrices.clear();
data.local_matrices
.extend_from_slice(matrices.local_matrices(entity));
data.custom_tints.clear();
data.custom_tints.extend(
instanced_mesh
.custom_data_slice()
.iter()
.map(|custom| custom.tint),
);
data.mesh_name.clear();
data.mesh_name.push_str(&instanced_mesh.mesh_name);
data.render_layer = world
.get::<crate::render::render_layer::RenderLayer>(entity)
.map(|layer| layer.0)
.unwrap_or(crate::render::render_layer::RenderLayer::WORLD);
data.visible = u32::from(
world
.get::<crate::ecs::primitives::Visibility>(entity)
.is_none_or(|v| v.visible),
);
data.parent_transform = world
.get::<crate::ecs::transform::components::GlobalTransform>(entity)
.map(|global| global.0)
.unwrap_or_else(nalgebra_glm::Mat4::identity);
true
}
fn instanced_object_data(
world: &World,
entity: crate::ecs::world::Entity,
) -> Option<crate::render::config::InstancedObjectData> {
let instanced_mesh = world.get::<crate::ecs::mesh::components::InstancedMesh>(entity)?;
let matrices = world.res::<crate::ecs::mesh::components::InstancedMeshMatrices>();
let model_matrices = matrices.model_matrices(entity);
let world_models = model_matrices.iter().map(|matrix| matrix.model).collect();
let world_normals = model_matrices
.iter()
.map(|matrix| matrix.normal_matrix)
.collect();
let local_matrices = matrices.local_matrices(entity).to_vec();
let custom_tints = instanced_mesh
.custom_data_slice()
.iter()
.map(|data| data.tint)
.collect();
let render_layer = world
.get::<crate::render::render_layer::RenderLayer>(entity)
.map(|layer| layer.0)
.unwrap_or(crate::render::render_layer::RenderLayer::WORLD);
let visible = u32::from(
world
.get::<crate::ecs::primitives::Visibility>(entity)
.is_none_or(|v| v.visible),
);
let parent_transform = world
.get::<crate::ecs::transform::components::GlobalTransform>(entity)
.map(|global| global.0)
.unwrap_or_else(nalgebra_glm::Mat4::identity);
Some(crate::render::config::InstancedObjectData {
world_models,
world_normals,
local_matrices,
custom_tints,
mesh_name: instanced_mesh.mesh_name.clone(),
render_layer,
visible,
parent_transform,
})
}
fn sync_objects_full(world: &mut World) {
let entities: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::RENDER_MESH)
.filter(|&entity| {
!world.ecs.worlds[CORE].entity_has_components(entity, crate::ecs::world::SKIN)
})
.collect();
let states: Vec<(
crate::ecs::world::Entity,
crate::render::config::DynamicRenderState,
)> = entities
.iter()
.map(|&entity| (entity, dynamic_object_state(world, entity)))
.collect();
let mesh_names: Vec<(crate::ecs::world::Entity, String)> = entities
.iter()
.filter_map(|&entity| {
world
.get::<crate::ecs::mesh::components::RenderMesh>(entity)
.map(|mesh| (entity, mesh.name.clone()))
})
.collect();
let skinned_entities: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::RENDER_MESH | crate::ecs::world::SKIN)
.collect();
let skinned_states: Vec<(
crate::ecs::world::Entity,
crate::render::config::DynamicRenderState,
Option<String>,
)> = skinned_entities
.iter()
.map(|&entity| {
let name = world
.get::<crate::ecs::mesh::components::RenderMesh>(entity)
.map(|mesh| mesh.name.clone());
(entity, dynamic_object_state(world, entity), name)
})
.collect();
let instanced: Vec<(
crate::ecs::world::Entity,
crate::render::config::InstancedObjectData,
)> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::INSTANCED_MESH)
.collect::<Vec<_>>()
.into_iter()
.filter_map(|entity| instanced_object_data(world, entity).map(|data| (entity, data)))
.collect();
let bounds: Vec<(
crate::ecs::world::Entity,
crate::render::config::RenderBounds,
)> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::BOUNDING_VOLUME)
.filter_map(|entity| {
world
.get::<crate::render::bounding_volume::BoundingVolume>(entity)
.map(|volume| {
(
entity,
crate::render::config::RenderBounds {
center: volume.obb.center,
sphere_radius: volume.sphere_radius,
},
)
})
})
.collect();
for (entity, state) in states {
world.set(entity, render_world::Transform(state.transform));
world.set(entity, render_world::Visible(state.visible != 0));
world.set(entity, render_world::MorphWeights(state.morph_weights));
world.set(entity, render_world::CullingMask(state.culling_mask));
world.set(entity, render_world::Layer(state.render_layer));
set_shadow_tag(world, entity);
}
for (entity, name) in mesh_names {
world.set(entity, render_world::MeshName(name));
}
for (entity, data) in instanced {
world.set(entity, data);
set_shadow_tag(world, entity);
}
{
let state = world.res_mut::<crate::render::config::RendererState>();
state.render_skinned_dynamic_state.clear();
state.render_skinned_mesh_names.clear();
for (entity, object_state, name) in skinned_states {
state
.render_skinned_dynamic_state
.insert(entity, object_state);
if let Some(name) = name {
state.render_skinned_mesh_names.insert(entity, name);
}
}
}
clear_render_component(world, render_world::BOUNDS);
for (entity, bound) in bounds {
world.set(entity, bound);
}
}
fn sync_objects_delta(world: &mut World, dirt: &SceneDirt) {
for &entity in &dirt.object_refresh {
let core = &world.ecs.worlds[CORE];
let has_mesh = core.entity_has_components(entity, crate::ecs::world::RENDER_MESH);
let skinned = core.entity_has_components(entity, crate::ecs::world::SKIN);
let instanced = core.entity_has_components(entity, crate::ecs::world::INSTANCED_MESH);
if has_mesh && !skinned {
write_object_render(world, entity);
} else if has_mesh && skinned {
let state = dynamic_object_state(world, entity);
world
.res_mut::<crate::render::config::RendererState>()
.render_skinned_dynamic_state
.insert(entity, state);
} else if !instanced {
clear_render_row(world, entity);
world
.res_mut::<crate::render::config::RendererState>()
.render_skinned_dynamic_state
.remove(&entity);
}
}
for &entity in &dirt.mesh_name_refresh {
let skinned = world.ecs.worlds[CORE].entity_has_components(entity, crate::ecs::world::SKIN);
match world.get::<crate::ecs::mesh::components::RenderMesh>(entity) {
Some(mesh) if !skinned => {
let name = mesh.name.clone();
world.set(entity, render_world::MeshName(name));
}
Some(mesh) => {
let name = mesh.name.clone();
world
.res_mut::<crate::render::config::RendererState>()
.render_skinned_mesh_names
.insert(entity, name);
}
None => {
world.ecs.worlds[RENDER].remove::<render_world::MeshName>(entity);
world
.res_mut::<crate::render::config::RendererState>()
.render_skinned_mesh_names
.remove(&entity);
}
}
}
for &entity in &dirt.instanced_refresh {
if let Some(mut data) = world.ecs.worlds[RENDER]
.get::<crate::render::config::InstancedObjectData>(entity)
.cloned()
{
if refill_instanced_object_data(world, entity, &mut data) {
world.set(entity, data);
set_shadow_tag(world, entity);
} else {
world.ecs.worlds[RENDER]
.remove::<crate::render::config::InstancedObjectData>(entity);
}
} else if let Some(data) = instanced_object_data(world, entity) {
world.set(entity, data);
set_shadow_tag(world, entity);
}
}
for &entity in &dirt.bounds_refresh {
match world.get::<crate::render::bounding_volume::BoundingVolume>(entity) {
Some(volume) => {
let bounds = crate::render::config::RenderBounds {
center: volume.obb.center,
sphere_radius: volume.sphere_radius,
};
world.set(entity, bounds);
}
None => {
world.ecs.worlds[RENDER].remove::<crate::render::config::RenderBounds>(entity);
}
}
}
for &entity in &dirt.object_state_dirty {
if world.ecs.worlds[CORE].entity_has_components(entity, crate::ecs::world::RENDER_MESH) {
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.mark_object_state_dirty(entity);
}
}
}
fn sync_entity_lists(world: &mut World) {
let shadow_skinned: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(
crate::ecs::world::SKIN
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::CASTS_SHADOW,
)
.collect();
let skinned_meshes: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(
crate::ecs::world::SKIN
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::GLOBAL_TRANSFORM,
)
.collect();
world
.res_mut::<crate::render::config::RendererState>()
.render_shadow_casters
.skinned = shadow_skinned.into_iter().collect();
world
.res_mut::<crate::render::config::RendererState>()
.render_skinned_meshes = skinned_meshes.into_iter().collect();
}
fn sync_lights(world: &mut World) {
let light_entities: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
.collect();
let mut collected: Vec<(
crate::ecs::world::Entity,
crate::render::config::RenderLightData,
nalgebra_glm::Mat4,
)> = Vec::with_capacity(light_entities.len());
for entity in light_entities {
if let (Some(light), Some(transform)) = (
world.get::<crate::ecs::light::components::Light>(entity),
world.get::<crate::ecs::transform::components::GlobalTransform>(entity),
) {
collected.push((
entity,
crate::render::config::RenderLightData {
light_type: match light.light_type {
crate::ecs::light::components::LightType::Directional => {
crate::render::config::RenderLightType::Directional
}
crate::ecs::light::components::LightType::Point => {
crate::render::config::RenderLightType::Point
}
crate::ecs::light::components::LightType::Spot => {
crate::render::config::RenderLightType::Spot
}
crate::ecs::light::components::LightType::Area => {
crate::render::config::RenderLightType::Area
}
},
color: light.color,
intensity: light.intensity,
range: light.range,
inner_cone_angle: light.inner_cone_angle,
outer_cone_angle: light.outer_cone_angle,
cast_shadows: light.cast_shadows,
shadow_bias: light.shadow_bias,
shadow_resolution: light.shadow_resolution,
shadow_distance: light.shadow_distance,
cookie_texture: light.cookie_texture.clone(),
area_shape: match light.area_shape {
crate::ecs::light::components::AreaLightShape::Rectangle => {
crate::render::config::RenderAreaLightShape::Rectangle
}
crate::ecs::light::components::AreaLightShape::Disk => {
crate::render::config::RenderAreaLightShape::Disk
}
crate::ecs::light::components::AreaLightShape::Sphere => {
crate::render::config::RenderAreaLightShape::Sphere
}
crate::ecs::light::components::AreaLightShape::Tube => {
crate::render::config::RenderAreaLightShape::Tube
}
},
area_width: light.area_width,
area_height: light.area_height,
area_radius: light.area_radius,
area_two_sided: light.area_two_sided,
area_emissive_texture: light.area_emissive_texture.clone(),
shadow_normal_bias: light.shadow_normal_bias,
shadow_softness: light.shadow_softness,
},
transform.0,
));
}
}
clear_render_domain(world, render_world::LIGHT);
for (entity, light, transform) in collected {
world.set(entity, light);
world.set(entity, render_world::Transform(transform));
}
}
fn sync_decals(world: &mut World) {
let collected: Vec<(
crate::ecs::world::Entity,
crate::render::config::RenderDecalData,
nalgebra_glm::Mat4,
bool,
)> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::DECAL | crate::ecs::GLOBAL_TRANSFORM)
.filter_map(|entity| {
let decal = world.get::<crate::ecs::decal::components::Decal>(entity)?;
let transform =
world.get::<crate::ecs::transform::components::GlobalTransform>(entity)?;
let visible = world
.get::<crate::ecs::primitives::Visibility>(entity)
.is_none_or(|visibility| visibility.visible);
Some((
entity,
crate::render::config::RenderDecalData {
texture: decal.texture.clone(),
emissive_texture: decal.emissive_texture.clone(),
emissive_strength: decal.emissive_strength,
color: decal.color,
size: decal.size,
depth: decal.depth,
normal_threshold: decal.normal_threshold,
fade_start: decal.fade_start,
fade_end: decal.fade_end,
},
transform.0,
visible,
))
})
.collect();
clear_render_domain(world, render_world::DECAL);
for (entity, decal, transform, visible) in collected {
world.set(entity, decal);
world.set(entity, render_world::Transform(transform));
world.set(entity, render_world::Visible(visible));
}
}
fn sync_emitters(world: &mut World) {
let collected: Vec<(
crate::ecs::world::Entity,
crate::render::particles::ParticleEmitter,
)> = world.ecs.worlds[CORE]
.query_entities(crate::ecs::PARTICLE_EMITTER)
.filter_map(|entity| {
world
.get::<crate::render::particles::ParticleEmitter>(entity)
.map(|emitter| (entity, emitter.clone()))
})
.collect();
clear_render_domain(world, render_world::EMITTER);
for (entity, emitter) in collected {
world.set(entity, render_world::RenderEmitter(emitter));
}
}
fn sync_water(world: &mut World) {
let collected: Vec<(
crate::ecs::world::Entity,
crate::render::config::RenderWaterData,
nalgebra_glm::Mat4,
)> = world
.query_ref::<(
&crate::ecs::water::components::Water,
&crate::ecs::transform::components::GlobalTransform,
)>()
.iter()
.filter(|(_, (water, _))| water.enabled)
.map(|(entity, (water, global_transform))| {
(
entity,
crate::render::config::RenderWaterData {
half_extents: water.half_extents,
tessellation: water.tessellation,
wave_amplitude: water.wave_amplitude,
wave_steepness: water.wave_steepness,
wave_length: water.wave_length,
wave_speed: water.wave_speed,
wave_direction_radians: water.wave_direction_radians,
shallow_color: water.shallow_color,
deep_color: water.deep_color,
depth_fade_distance: water.depth_fade_distance,
edge_foam_distance: water.edge_foam_distance,
foam_amount: water.foam_amount,
foam_color: water.foam_color,
roughness: water.roughness,
fresnel_power: water.fresnel_power,
reflection_strength: water.reflection_strength,
refraction_strength: water.refraction_strength,
specular_strength: water.specular_strength,
},
global_transform.0,
)
})
.collect();
clear_render_domain(world, render_world::WATER);
for (entity, water, transform) in collected {
world.set(entity, water);
world.set(entity, render_world::Transform(transform));
}
}
#[cfg(feature = "meshlet")]
fn meshlet_material_id(
registry: &crate::ecs::material::resources::MaterialRegistry,
material_ref: Option<&crate::ecs::material::components::MaterialRef>,
) -> u32 {
use crate::render::generational_registry::{registry_entry, registry_lookup_index};
material_ref.map_or(0, |reference| {
reference
.id
.and_then(|id| {
registry_entry(®istry.registry, id.index, id.generation).map(|_| id.index + 1)
})
.or_else(|| {
registry_lookup_index(®istry.registry, &reference.name).and_then(
|(index, _generation)| {
registry.registry.entries[index as usize]
.as_ref()
.map(|_| index + 1)
},
)
})
.unwrap_or(0)
})
}
#[cfg(feature = "meshlet")]
fn sync_meshlets(world: &mut World, dirt: &SceneDirt) {
let registry_version = {
let registry = world.res::<crate::ecs::material::resources::MaterialRegistry>();
(registry.version, registry.names_version)
};
let materials_moved = world
.res::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.meshlet_materials_version_seen
!= registry_version;
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.meshlet_materials_version_seen = registry_version;
let refresh: Vec<crate::ecs::world::Entity> = if dirt.full || materials_moved {
world.ecs.worlds[CORE]
.query_entities(crate::ecs::world::MESHLET_MESH)
.collect()
} else if dirt.meshlet_refresh.is_empty() {
return;
} else {
dirt.meshlet_refresh.iter().copied().collect()
};
let mut placements: Vec<(
crate::ecs::world::Entity,
crate::render::config::MeshletPlacement,
)> = Vec::with_capacity(refresh.len());
let mut retired: Vec<crate::ecs::world::Entity> = Vec::new();
{
let registry = world.res::<crate::ecs::material::resources::MaterialRegistry>();
for &entity in &refresh {
let Some(meshlet_mesh) =
world.get::<crate::ecs::meshlet::components::MeshletMesh>(entity)
else {
retired.push(entity);
continue;
};
let Some(global_transform) =
world.get::<crate::ecs::transform::components::GlobalTransform>(entity)
else {
retired.push(entity);
continue;
};
let material_id = meshlet_material_id(
registry,
world.get::<crate::ecs::material::components::MaterialRef>(entity),
);
placements.push((
entity,
crate::render::config::MeshletPlacement {
transform: global_transform.0,
asset_id: meshlet_mesh.asset_id,
material_id,
},
));
}
}
let mut assets = std::mem::take(
&mut world
.res_mut::<crate::render::config::RendererState>()
.meshlet_assets,
);
for (_, placement) in &placements {
if assets.contains_key(&placement.asset_id) {
continue;
}
let Some(asset) = world
.res::<crate::ecs::meshlet::resources::MeshletAssets>()
.get(placement.asset_id)
.cloned()
else {
continue;
};
assets.insert(placement.asset_id, asset);
}
world
.res_mut::<crate::render::config::RendererState>()
.meshlet_assets = assets;
if dirt.full {
clear_render_component(world, render_world::MESHLET);
}
let touched = dirt.full || !placements.is_empty() || !retired.is_empty();
for (entity, placement) in placements {
world.set(entity, placement);
}
for entity in retired {
world.ecs.worlds[RENDER].remove_components(entity, render_world::MESHLET);
}
if touched {
let state = world.res_mut::<crate::render::config::RendererState>();
state.meshlet_placements_generation = state.meshlet_placements_generation.wrapping_add(1);
}
}
fn sync_cloth(world: &mut World) {
let cloth_bodies: Vec<(
crate::ecs::world::Entity,
crate::render::config::RenderClothData,
)> = world
.query_ref::<(
&crate::ecs::cloth::components::Cloth,
&crate::ecs::transform::components::GlobalTransform,
&crate::ecs::mesh::components::RenderMesh,
)>()
.iter()
.map(|(entity, (cloth, _, _))| {
(
entity,
crate::render::config::RenderClothData {
columns: cloth.columns,
rows: cloth.rows,
width: cloth.width,
height: cloth.height,
pinning: match cloth.pinning {
crate::ecs::cloth::components::ClothPinning::TopRow => {
crate::render::config::RenderClothPinning::TopRow
}
crate::ecs::cloth::components::ClothPinning::TopCorners => {
crate::render::config::RenderClothPinning::TopCorners
}
crate::ecs::cloth::components::ClothPinning::None => {
crate::render::config::RenderClothPinning::None
}
},
stiffness: cloth.stiffness,
damping: cloth.damping,
substeps: cloth.substeps,
solver_iterations: cloth.solver_iterations,
gravity: cloth.gravity,
wind_response: cloth.wind_response,
ground_height: cloth.ground_height,
texture_tiling: cloth.texture_tiling,
reset_epoch: cloth.reset_epoch,
},
)
})
.collect();
clear_render_component(world, render_world::CLOTH);
for (entity, cloth) in cloth_bodies {
world.set(entity, cloth);
}
}
fn sync_texts(world: &mut World) {
let text_entities: Vec<crate::ecs::world::Entity> = world.ecs.worlds[CORE]
.query_entities(
crate::ecs::world::TEXT
| crate::ecs::world::VISIBILITY
| crate::ecs::world::GLOBAL_TRANSFORM,
)
.collect();
let texts: Vec<(crate::ecs::world::Entity, crate::render::config::RenderText)> = text_entities
.into_iter()
.filter_map(|entity| {
if !world
.get::<crate::ecs::primitives::Visibility>(entity)
.map(|visibility| visibility.visible)
.unwrap_or(false)
{
return None;
}
let text = world.get::<crate::ecs::text::components::Text>(entity)?;
text.cached_mesh.as_ref()?;
let transform = world
.get::<crate::ecs::transform::components::GlobalTransform>(entity)?
.0;
let char_count = world
.res::<crate::ecs::text::resources::TextState>()
.cache
.get_text(text.text_index)
.map(|content| {
content
.chars()
.filter(|character| *character != '\n')
.count()
})
.unwrap_or(0);
let character_colors = world
.get::<crate::ecs::text::components::TextCharacterColors>(entity)
.map(|colors| colors.colors.clone());
Some((
entity,
crate::render::config::RenderText {
mesh: text.cached_mesh.clone()?,
color: text.properties.color,
outline_color: text.properties.outline_color,
outline_width: text.properties.outline_width,
smoothing: text.properties.smoothing,
billboard: text.billboard,
transform,
char_count,
character_colors,
},
))
})
.collect();
clear_render_component(world, render_world::TEXT);
for (entity, text) in texts {
world.set(entity, text);
}
}
fn sync_selection_outline(world: &mut World) {
let outline_entities: Vec<crate::ecs::world::Entity> = if world
.res::<crate::render::config::RendererState>()
.active_view
.selection_outline_enabled
{
let mut seeds: Vec<crate::ecs::world::Entity> = Vec::new();
if let Some(active) = world
.res::<crate::ecs::graphics::selection::Selection>()
.active_entity
{
seeds.push(active);
}
for entity in &world
.res::<crate::ecs::graphics::selection::Selection>()
.entities
{
if !seeds.contains(entity) {
seeds.push(*entity);
}
}
let mut entities: Vec<crate::ecs::world::Entity> = Vec::with_capacity(seeds.len() * 4);
for seed in &seeds {
entities.push(*seed);
for descendant in crate::ecs::transform::queries::query_descendants(world, *seed) {
entities.push(descendant);
}
}
entities
} else {
Vec::new()
};
let render = &mut world.ecs.worlds[RENDER];
let previous: Vec<crate::ecs::world::Entity> = render
.query_tag_type::<render_world::SelectionOutline>()
.collect();
for entity in previous {
render.remove_tag_type::<render_world::SelectionOutline>(entity);
}
for entity in outline_entities {
render.add_tag_type::<render_world::SelectionOutline>(entity);
}
world
.res_mut::<crate::render::config::RendererState>()
.render_selection_outline_color = world
.res_mut::<crate::ecs::graphics::selection::Selection>()
.outline_color;
}
pub fn render_sync_system(world: &mut World) {
let _span = tracing::info_span!("render_sync").entered();
let dirt = collect_scene_dirt(world);
#[cfg(feature = "meshlet")]
sync_meshlets(world, &dirt);
if dirt.full {
world
.res_mut::<crate::ecs::graphics::scene_sync::RenderSceneSync>()
.materials_dirty = true;
world
.res_mut::<crate::render::mesh_state::MeshRenderState>()
.request_full_rebuild();
world
.res_mut::<crate::render::config::RendererState>()
.skinned_generation = world
.res_mut::<crate::render::config::RendererState>()
.skinned_generation
.wrapping_add(1);
sync_objects_full(world);
sync_entity_lists(world);
sync_lights(world);
sync_decals(world);
sync_emitters(world);
sync_water(world);
sync_cloth(world);
sync_texts(world);
sync_selection_outline(world);
return;
}
sync_objects_delta(world, &dirt);
if dirt.skinned_objects {
world
.res_mut::<crate::render::config::RendererState>()
.skinned_generation = world
.res_mut::<crate::render::config::RendererState>()
.skinned_generation
.wrapping_add(1);
}
if dirt.entity_lists {
sync_entity_lists(world);
}
if dirt.lights {
sync_lights(world);
}
if dirt.decals {
sync_decals(world);
}
if dirt.emitters {
sync_emitters(world);
}
if dirt.water {
sync_water(world);
}
if dirt.cloth {
sync_cloth(world);
}
if dirt.texts {
sync_texts(world);
}
sync_selection_outline(world);
}