use super::super::configuration::SimulationConfiguration;
use super::scene::{Scene, SceneStatus, find_scene_ancestor};
use crate::geometry::{Material, Point, StaticMeshHandle, StaticMeshSettings, Triangle};
use bevy::asset::AssetId;
use bevy::mesh::{Indices, VertexAttributeValues};
use bevy::prelude::{
Assets, ChildOf, Commands, Component, DetectChanges, Entity, GlobalTransform, Mesh, Mesh3d, On,
Query, Ref, Reflect, ReflectComponent, Remove, Res, Transform, Vec3, With,
};
#[derive(Component, Reflect, Copy, Clone, Debug)]
#[reflect(Component)]
#[require(Mesh3d, Transform)]
pub struct StaticMesh;
#[derive(Component, Debug)]
pub(crate) struct SpawnedStaticMesh {
pub(crate) scene_entity: Entity,
pub(crate) mesh_asset_id: AssetId<Mesh>,
pub(crate) material: Material,
pub(crate) handle: StaticMeshHandle,
}
#[allow(clippy::type_complexity)]
pub(crate) fn sync_static_meshes<C: SimulationConfiguration>(
static_meshes: Query<
(
Entity,
&Mesh3d,
Ref<GlobalTransform>,
Option<&Material>,
Option<&SpawnedStaticMesh>,
),
With<StaticMesh>,
>,
parents: Query<&ChildOf>,
global_transforms: Query<&GlobalTransform>,
mut scenes: Query<(&mut Scene<C>, &mut SceneStatus)>,
mesh_assets: Res<Assets<Mesh>>,
mut commands: Commands,
) {
for (entity, mesh_3d, global_transform, material_option, spawned_static_mesh) in &static_meshes
{
let scene_entity_option = find_scene_ancestor::<C>(entity, &parents, &scenes);
let mesh_asset_id = mesh_3d.0.id();
let material = material_option.copied().unwrap_or_default();
let registration_is_current = spawned_static_mesh.is_some_and(|spawned_static_mesh| {
scene_entity_option == Some(spawned_static_mesh.scene_entity)
&& spawned_static_mesh.mesh_asset_id == mesh_asset_id
&& spawned_static_mesh.material == material
});
if registration_is_current && !global_transform.is_changed() {
continue;
}
if let Some(spawned_static_mesh) = spawned_static_mesh {
deregister_static_mesh(spawned_static_mesh, &mut scenes);
}
let registration = scene_entity_option.and_then(|scene_entity| {
try_register_static_mesh(
entity,
scene_entity,
mesh_asset_id,
material,
mesh_3d,
global_transform.as_ref(),
&global_transforms,
&mesh_assets,
&mut scenes,
)
});
let mut entity_commands = commands.entity(entity);
match registration {
Some(registration) => {
entity_commands.insert(registration);
if material_option.is_none() {
entity_commands.insert(material);
}
}
None => {
entity_commands.remove::<SpawnedStaticMesh>();
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn try_register_static_mesh<C: SimulationConfiguration>(
entity: Entity,
scene_entity: Entity,
mesh_asset_id: AssetId<Mesh>,
material: Material,
mesh_3d: &Mesh3d,
global_transform: &GlobalTransform,
global_transforms: &Query<&GlobalTransform>,
mesh_assets: &Assets<Mesh>,
scenes: &mut Query<(&mut Scene<C>, &mut SceneStatus)>,
) -> Option<SpawnedStaticMesh> {
let mesh = mesh_assets.get(&mesh_3d.0)?;
let (vertices, triangles) = match extract_geometry(mesh) {
Some(geometry) => geometry,
None => {
bevy::log::warn_once!("Mesh3d incompatible with StaticMesh; skipping.");
return None;
}
};
let scene_inverse = global_transforms
.get(scene_entity)
.map(|transform| transform.affine().inverse())
.unwrap_or_default();
let mesh_to_scene = scene_inverse * global_transform.affine();
let vertices: Vec<Point> = vertices
.into_iter()
.map(|vertex| {
let point = mesh_to_scene.transform_point3(Vec3::new(vertex.x, vertex.y, vertex.z));
Point::new(point.x, point.y, point.z)
})
.collect();
let material_indices = vec![0; triangles.len()];
let (mut scene, mut scene_status) = scenes.get_mut(scene_entity).ok()?;
let inner_mesh = crate::geometry::StaticMesh::try_new(
&scene.0,
&StaticMeshSettings {
vertices: &vertices,
triangles: &triangles,
material_indices: &material_indices,
materials: &[material],
},
)
.inspect_err(|error| {
bevy::log::error!("failed to create static mesh for {entity:?}: {error:?}");
})
.ok()?;
let handle = scene.0.add_static_mesh(inner_mesh);
scene_status.commit_needed = true;
Some(SpawnedStaticMesh {
scene_entity,
mesh_asset_id,
material,
handle,
})
}
pub(crate) fn on_static_mesh_removed<C: SimulationConfiguration>(
event: On<Remove, StaticMesh>,
handles: Query<&SpawnedStaticMesh>,
mut scenes: Query<(&mut Scene<C>, &mut SceneStatus)>,
) {
let entity = event.entity;
let Ok(static_mesh) = handles.get(entity) else {
return;
};
deregister_static_mesh(static_mesh, &mut scenes);
}
fn deregister_static_mesh<C: SimulationConfiguration>(
static_mesh: &SpawnedStaticMesh,
scenes: &mut Query<(&mut Scene<C>, &mut SceneStatus)>,
) {
let Ok((mut scene, mut scene_status)) = scenes.get_mut(static_mesh.scene_entity) else {
return;
};
scene.0.remove_static_mesh(static_mesh.handle);
scene_status.commit_needed = true;
}
fn extract_geometry(mesh: &Mesh) -> Option<(Vec<Point>, Vec<Triangle>)> {
let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
let vertices: Vec<Point> = match positions {
VertexAttributeValues::Float32x3(vertices) => vertices
.iter()
.map(|&[x, y, z]| Point::new(x, y, z))
.collect(),
_ => {
return None;
}
};
let triangles: Vec<Triangle> = match mesh.indices()? {
Indices::U16(indices) => indices
.chunks_exact(3)
.map(|c| Triangle::new(c[0] as i32, c[1] as i32, c[2] as i32))
.collect(),
Indices::U32(indices) => indices
.chunks_exact(3)
.map(|c| Triangle::new(c[0] as i32, c[1] as i32, c[2] as i32))
.collect(),
};
Some((vertices, triangles))
}