use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::navmesh::{RecastNavMeshConfig, generate_navmesh_recast};
use crate::ecs::physics::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::transform::queries::query_descendants;
use crate::ecs::world::components::Parent;
use crate::ecs::world::{COLLIDER, NAME, PARENT, RIGID_BODY};
use crate::editor::selection::EntitySelection;
use crate::prelude::*;
pub fn navmesh_section_ui(selection: &EntitySelection, world: &mut World, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.label(egui::RichText::new("NavMesh").strong());
ui.separator();
let navmesh = &world.resources.navmesh;
let triangle_count = navmesh.triangles.len();
let vertex_count = navmesh.vertices.len();
let connection_count: usize = navmesh.adjacency.values().map(|v| v.len()).sum();
if triangle_count > 0 {
ui.label(format!("Triangles: {}", triangle_count));
ui.label(format!("Vertices: {}", vertex_count));
ui.label(format!("Connections: {}", connection_count));
ui.separator();
} else {
ui.label("No navmesh generated");
ui.separator();
}
if let Some(selected_entity) = selection.primary() {
if ui.button("Generate NavMesh").clicked() {
generate_navmesh_from_entity(world, selected_entity);
}
ui.label(
egui::RichText::new("Generates from selected entity and children")
.small()
.weak(),
);
} else {
ui.add_enabled(false, egui::Button::new("Generate NavMesh"));
ui.label(
egui::RichText::new("Select an entity to generate navmesh")
.small()
.weak(),
);
}
if triangle_count > 0 && ui.button("Clear NavMesh").clicked() {
world.resources.navmesh.clear();
}
});
}
struct TransformedMesh {
vertices: Vec<[f32; 3]>,
indices: Vec<[u32; 3]>,
}
fn extract_mesh_with_transform(
world: &World,
entity: Entity,
transform: nalgebra_glm::Mat4,
) -> Option<TransformedMesh> {
let mesh_name = world.get_render_mesh(entity)?.name.clone();
let mesh = registry_entry_by_name(&world.resources.mesh_cache.registry, &mesh_name)?;
let vertices: Vec<[f32; 3]> = mesh
.vertices
.iter()
.map(|vertex| {
let pos = nalgebra_glm::vec4(
vertex.position[0],
vertex.position[1],
vertex.position[2],
1.0,
);
let transformed = transform * pos;
[transformed.x, transformed.y, transformed.z]
})
.collect();
let indices: Vec<[u32; 3]> = mesh
.indices
.chunks(3)
.filter(|chunk| chunk.len() == 3)
.map(|chunk| [chunk[0], chunk[1], chunk[2]])
.collect();
Some(TransformedMesh { vertices, indices })
}
fn extract_transformed_mesh(world: &World, entity: Entity) -> Option<TransformedMesh> {
let global_transform = world
.get_global_transform(entity)
.map(|t| t.0)
.unwrap_or_else(nalgebra_glm::Mat4::identity);
extract_mesh_with_transform(world, entity, global_transform)
}
fn extract_local_mesh(world: &World, entity: Entity) -> Option<TransformedMesh> {
extract_mesh_with_transform(world, entity, nalgebra_glm::Mat4::identity())
}
fn collect_entity_and_descendants(world: &World, entity: Entity) -> Vec<Entity> {
let mut entities = vec![entity];
entities.extend(query_descendants(world, entity));
entities
}
fn generate_navmesh_from_entity(world: &mut World, entity: Entity) {
let mut all_vertices: Vec<[f32; 3]> = Vec::new();
let mut all_indices: Vec<[u32; 3]> = Vec::new();
let entities_to_process = collect_entity_and_descendants(world, entity);
for entity in entities_to_process {
if let Some(mesh) = extract_transformed_mesh(world, entity) {
let base_index = all_vertices.len() as u32;
all_vertices.extend(mesh.vertices);
all_indices.extend(mesh.indices.into_iter().map(|tri| {
[
base_index + tri[0],
base_index + tri[1],
base_index + tri[2],
]
}));
}
}
if all_indices.is_empty() {
return;
}
let config = RecastNavMeshConfig::default();
if let Some(navmesh) = generate_navmesh_recast(&all_vertices, &all_indices, &config) {
world.resources.navmesh = navmesh;
}
}
pub fn colliders_section_ui(selection: &EntitySelection, world: &mut World, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.label(egui::RichText::new("Colliders").strong());
ui.separator();
let collider_count = world.resources.physics.collider_set.len();
ui.label(format!("Colliders: {}", collider_count));
ui.separator();
if let Some(selected_entity) = selection.primary() {
if ui.button("Generate Colliders").clicked() {
generate_colliders_from_entity(world, selected_entity);
}
ui.label(
egui::RichText::new(
"Generates trimesh colliders from selected entity and children",
)
.small()
.weak(),
);
} else {
ui.add_enabled(false, egui::Button::new("Generate Colliders"));
ui.label(
egui::RichText::new("Select an entity to generate colliders")
.small()
.weak(),
);
}
});
}
fn generate_colliders_from_entity(world: &mut World, entity: Entity) {
let entities_to_process = collect_entity_and_descendants(world, entity);
let mut collider_count = 0;
for entity in entities_to_process {
let Some(mesh) = extract_local_mesh(world, entity) else {
continue;
};
if mesh.indices.is_empty() {
continue;
}
let collision_entity = world.spawn_entities(
NAME | LOCAL_TRANSFORM
| GLOBAL_TRANSFORM
| LOCAL_TRANSFORM_DIRTY
| PARENT
| RIGID_BODY
| COLLIDER,
1,
)[0];
world.set_parent(collision_entity, Parent(Some(entity)));
if let Some(name) = world.get_name_mut(collision_entity) {
name.0 = format!("Mesh Collision {}", collider_count);
}
if let Some(rigid_body) = world.get_rigid_body_mut(collision_entity) {
*rigid_body = RigidBodyComponent::new_static();
}
if let Some(collider) = world.get_collider_mut(collision_entity) {
*collider = ColliderComponent {
shape: ColliderShape::TriMesh {
vertices: mesh.vertices,
indices: mesh.indices,
},
friction: 0.7,
restitution: 0.0,
..Default::default()
};
}
collider_count += 1;
}
}