jackdaw 0.3.1

A 3D level editor built with Bevy
Documentation
mod brp_client;
mod build;
mod save_load;
pub mod toolbar;
mod visualization;

use bevy::prelude::*;
use bevy_rerecast::{TriMeshFromBevyMesh as _, prelude::*, rerecast::TriMesh};

use crate::{EditorEntity, EditorMeta};

pub use toolbar::NavmeshToolbar;
pub use visualization::NavmeshVizConfig;

pub struct NavmeshPlugin;

impl Plugin for NavmeshPlugin {
    fn build(&self, app: &mut App) {
        app.register_type_data::<jackdaw_jsn::NavmeshRegion, crate::ReflectEditorMeta>();
        app.add_plugins(
            NavmeshPlugins::default()
                .build()
                .disable::<bevy_rerecast::debug::NavmeshDebugPlugin>(),
        );
        app.set_navmesh_backend(scene_mesh_backend);
        app.init_resource::<NavmeshObstacles>()
            .init_resource::<NavmeshHandleRes>()
            .init_resource::<NavmeshState>();
        app.add_plugins((
            build::plugin,
            brp_client::plugin,
            save_load::plugin,
            toolbar::plugin,
            visualization::plugin,
        ));
    }
}

fn scene_mesh_backend(
    input: In<NavmeshSettings>,
    meshes: Res<Assets<Mesh>>,
    mesh_entities: Query<(Entity, &GlobalTransform, &Mesh3d), Without<EditorEntity>>,
    brp_obstacles: Res<NavmeshObstacles>,
) -> TriMesh {
    let mut result = brp_obstacles.0.clone();
    for (entity, global_tf, mesh_handle) in mesh_entities.iter() {
        if input.filter.as_ref().is_some_and(|f| !f.contains(&entity)) {
            continue;
        }
        let Some(mesh) = meshes.get(mesh_handle) else {
            continue;
        };
        let transform = global_tf.compute_transform();
        let transformed = mesh.clone().transformed_by(transform);
        if let Some(tri) = TriMesh::from_mesh(&transformed) {
            result.extend(tri);
        }
    }
    result
}

#[derive(Resource, Deref, DerefMut, Default)]
pub struct NavmeshObstacles(pub TriMesh);

#[derive(Resource, Default, Deref, DerefMut)]
pub struct NavmeshHandleRes(pub Handle<Navmesh>);

#[derive(Resource, Default)]
pub struct NavmeshState {
    pub status: NavmeshStatus,
}

#[derive(Default, Clone, Debug)]
pub enum NavmeshStatus {
    #[default]
    Idle,
    FetchingScene,
    Building,
    Ready,
    Error(String),
}

impl std::fmt::Display for NavmeshStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Idle => write!(f, "Navmesh: Idle"),
            Self::FetchingScene => write!(f, "Navmesh: Fetching scene..."),
            Self::Building => write!(f, "Navmesh: Building..."),
            Self::Ready => write!(f, "Navmesh: Ready"),
            Self::Error(e) => write!(f, "Navmesh: Error - {e}"),
        }
    }
}

impl EditorMeta for jackdaw_jsn::NavmeshRegion {
    fn category() -> &'static str {
        "Navmesh"
    }
}

pub fn spawn_navmesh_entity(commands: &mut Commands) -> Entity {
    commands
        .spawn((
            Name::new("Navmesh"),
            Transform::from_scale(Vec3::splat(10.0)),
            jackdaw_jsn::NavmeshRegion::default(),
        ))
        .id()
}