use super::super::configuration::SimulationConfiguration;
use super::scene::{Scene, SceneStatus, find_scene_ancestor};
use crate::geometry::{InstancedMeshHandle, InstancedMeshSettings, Matrix4};
use bevy::prelude::{
ChildOf, Commands, Component, DetectChanges, Entity, GlobalTransform, On, Query, Ref, Reflect,
ReflectComponent, Remove,
};
#[derive(Component, Reflect, Copy, Clone, Debug)]
#[reflect(Component)]
#[require(GlobalTransform)]
#[relationship(relationship_target = SubSceneOf)]
pub struct InstancedMesh(pub Entity);
#[derive(Component, Clone, Debug)]
#[relationship_target(relationship = InstancedMesh)]
pub struct SubSceneOf(Vec<Entity>);
#[derive(Component, Debug)]
pub(crate) struct SpawnedInstancedMesh {
pub(crate) scene_entity: Entity,
pub(crate) sub_scene_entity: Entity,
pub(crate) handle: InstancedMeshHandle,
}
pub(crate) fn sync_instanced_meshes<C: SimulationConfiguration>(
instanced_meshes: Query<(
Entity,
&InstancedMesh,
Ref<GlobalTransform>,
Option<&SpawnedInstancedMesh>,
)>,
parents: Query<&ChildOf>,
mut scenes: Query<(&mut Scene<C>, &mut SceneStatus)>,
mut commands: Commands,
) {
for (entity, instanced_mesh, global_transform, spawned_instanced_mesh_option) in
&instanced_meshes
{
let scene_entity_option = find_scene_ancestor::<C>(entity, &parents, &scenes);
let sub_scene_entity = instanced_mesh.0;
let registration_is_current =
spawned_instanced_mesh_option.is_some_and(|spawned_instanced_mesh| {
scene_entity_option == Some(spawned_instanced_mesh.scene_entity)
&& spawned_instanced_mesh.sub_scene_entity == sub_scene_entity
});
if registration_is_current {
if global_transform.is_changed()
&& let Some(spawned_instanced_mesh) = spawned_instanced_mesh_option
{
update_instanced_mesh_transform(
spawned_instanced_mesh,
&global_transform,
&mut scenes,
);
}
continue;
}
if let Some(spawned_instanced_mesh) = spawned_instanced_mesh_option {
deregister_instanced_mesh(spawned_instanced_mesh, &mut scenes);
}
let registration = scene_entity_option.and_then(|scene_entity| {
try_register_instanced_mesh(
entity,
scene_entity,
sub_scene_entity,
&global_transform,
&mut scenes,
)
});
match registration {
Some(registration) => commands.entity(entity).insert(registration),
None => commands.entity(entity).remove::<SpawnedInstancedMesh>(),
};
}
}
fn try_register_instanced_mesh<C: SimulationConfiguration>(
entity: Entity,
scene_entity: Entity,
sub_scene_entity: Entity,
global_transform: &GlobalTransform,
scenes: &mut Query<(&mut Scene<C>, &mut SceneStatus)>,
) -> Option<SpawnedInstancedMesh> {
let sub_scene = scenes.get(sub_scene_entity).ok()?.0.0.clone();
let (mut parent_scene, mut parent_scene_status) = scenes.get_mut(scene_entity).ok()?;
let inner_mesh = crate::geometry::InstancedMesh::try_new(
&parent_scene.0,
&InstancedMeshSettings {
sub_scene,
transform: Matrix4::from(*global_transform),
},
)
.inspect_err(|error| {
bevy::log::error!("failed to create instanced mesh for {entity:?}: {error:?}");
})
.ok()?;
let handle = parent_scene.0.add_instanced_mesh(inner_mesh);
parent_scene_status.commit_needed = true;
Some(SpawnedInstancedMesh {
scene_entity,
sub_scene_entity,
handle,
})
}
pub(crate) fn on_instanced_mesh_removed<C: SimulationConfiguration>(
event: On<Remove, InstancedMesh>,
handles: Query<&SpawnedInstancedMesh>,
mut scenes: Query<(&mut Scene<C>, &mut SceneStatus)>,
) {
let entity = event.entity;
let Ok(instanced_mesh) = handles.get(entity) else {
return;
};
deregister_instanced_mesh(instanced_mesh, &mut scenes);
}
fn update_instanced_mesh_transform<C: SimulationConfiguration>(
instanced_mesh: &SpawnedInstancedMesh,
global_transform: &GlobalTransform,
scenes: &mut Query<(&mut Scene<C>, &mut SceneStatus)>,
) {
let Ok((mut scene, mut scene_status)) = scenes.get_mut(instanced_mesh.scene_entity) else {
return;
};
scene
.0
.update_instanced_mesh_transform(instanced_mesh.handle, Matrix4::from(*global_transform));
scene_status.commit_needed = true;
}
fn deregister_instanced_mesh<C: SimulationConfiguration>(
instanced_mesh: &SpawnedInstancedMesh,
scenes: &mut Query<(&mut Scene<C>, &mut SceneStatus)>,
) {
let Ok((mut scene, mut scene_status)) = scenes.get_mut(instanced_mesh.scene_entity) else {
return;
};
scene.0.remove_instanced_mesh(instanced_mesh.handle);
scene_status.commit_needed = true;
}