You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
17 KiB
Rust

use bevy::{asset::AssetEvents, gltf::Gltf, prelude::*, utils::HashMap};
use monologue_trees::debug::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "GLTF Inspector".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
}),
DebugInfoPlugin,
))
.add_event::<ResetScene>()
.add_event::<SpawnScene>()
.init_resource::<Current>()
.add_systems(Startup, spawn_ui)
.add_systems(
Update,
(
drag_and_drop,
loading,
reset_scene.before(spawn_scene),
spawn_scene.after(reset_scene),
wire_up_scenes,
control_scene,
wire_up_animations,
control_animation,
wire_up_cameras,
control_camera,
control_default_camera,
),
)
.run();
}
#[derive(Component)]
struct SceneMarker;
#[derive(Component)]
struct MainCamera;
#[derive(Component)]
struct MainUi;
#[derive(Component)]
struct UiTitle;
#[derive(Component)]
struct InstructionsUi;
#[derive(Component)]
struct SceneSelectUi;
#[derive(Component, PartialEq, Debug)]
struct SceneButton(Handle<Scene>);
#[derive(Component)]
struct AnimationSelectUi;
#[derive(Component, PartialEq, Debug)]
struct AnimationButton(Handle<AnimationClip>);
#[derive(Component)]
struct CameraSelectUi;
#[derive(Component, PartialEq, Debug)]
struct CameraButton(Entity);
#[derive(Resource, Default)]
struct Current {
gltf: Handle<Gltf>,
scenes: HashMap<String, Handle<Scene>>,
animations: HashMap<String, Handle<AnimationClip>>,
}
#[derive(Event)]
struct ResetScene;
#[derive(Event)]
struct SpawnScene(Handle<Scene>);
fn drag_and_drop(
mut events: EventReader<FileDragAndDrop>,
server: Res<AssetServer>,
mut current: ResMut<Current>,
) {
events
.iter()
.filter_map(|event| {
if let FileDragAndDrop::DroppedFile { path_buf, .. } = event {
info!("Drag+Drop file: {:?}", path_buf);
Some(path_buf)
} else {
None
}
})
.map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str")))
.for_each(|handle| current.gltf = handle.clone());
}
fn spawn_ui(mut commands: Commands) {
// TODO: Warn no camera (hidden)
// TODO: Scene select container
// TODO: Animation Play/Pause Placeholder
commands.spawn((
Camera3dBundle { ..default() },
UiCameraConfig { show_ui: true },
MainCamera,
));
commands
.spawn((
NodeBundle {
style: Style {
flex_wrap: FlexWrap::Wrap,
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::SpaceAround,
width: Val::Percent(100.0),
height: Val::Percent(100.0),
..default()
},
..default()
},
MainUi,
))
.with_children(|parent| {
// TOOD: Prompt to drag+drop Gltf/Gltf file
parent.spawn((
TextBundle {
text: Text {
sections: vec![TextSection {
value: String::from("Drag and Drop .gltf/.glb file"),
style: TextStyle {
color: Color::WHITE,
..default()
},
}],
..default()
},
..default()
},
InstructionsUi,
));
parent
.spawn((
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
SceneSelectUi,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section("Scenes", TextStyle { ..default() }),
UiTitle,
));
});
parent
.spawn((
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
AnimationSelectUi,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section("Animations", TextStyle { ..default() }),
UiTitle,
));
});
parent
.spawn((
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
CameraSelectUi,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section("Cameras", TextStyle { ..default() }),
UiTitle,
));
});
});
}
fn wire_up_scenes(
mut events: EventReader<AssetEvent<Scene>>,
root: Query<Entity, With<SceneSelectUi>>,
root_children: Query<&Children, With<SceneSelectUi>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
current: Res<Current>,
existing: Query<&SceneButton, Without<UiTitle>>,
) {
events.iter().for_each(|event| {
// Cleanup unused buttons
root_children.iter().for_each(|children| {
// Iterate over UI children elements
children
.iter()
// Filter to just the buttons we want to despawn
.filter(|&child| {
// Check each child entity against the currently loaded scenes
// Note: !(_.any(...)) (not-any)
!((*current).scenes.iter().any(|(_, handle)| {
// If the current entitie's scene button corresponds to one of the scenes
// loaded, continue, otherwise
existing.get(*child).map_or(false, |scene_button| {
*scene_button == SceneButton(handle.cast_weak::<Scene>())
})
}))
})
.for_each(|&e| {
commands.entity(e).despawn_recursive();
});
});
// Add buttons
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
let (name, _) = gltfs
.get(&current.gltf)
.expect("Load current gltf")
.named_scenes
.iter()
.find(|(_, h)| h == &handle)
.expect("Find this scene");
let mut ui = commands.entity(root.single());
ui.with_children(|parent| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
SceneButton(handle.clone()),
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: name.clone(),
style: TextStyle {
color: Color::WHITE,
..default()
},
}],
..default()
},
..default()
});
});
});
}
AssetEvent::Removed { .. } => warn!("IGNORED Remove scene in list"),
}
});
}
/// Translate UI button presses into scene spawn events
fn control_scene(
mut events: EventWriter<SpawnScene>,
interactions: Query<(&Interaction, &SceneButton), (Changed<Interaction>, With<Button>)>,
) {
// Handle UI buttons
interactions
.iter()
.for_each(|(interaction, SceneButton(handle))| match interaction {
Interaction::Pressed => events.send(SpawnScene(handle.clone())),
_ => (),
});
}
fn wire_up_animations(
mut events: EventReader<AssetEvent<AnimationClip>>,
root: Query<Entity, With<AnimationSelectUi>>,
root_children: Query<&Children, With<AnimationSelectUi>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
current: Res<Current>,
existing: Query<&AnimationButton, Without<UiTitle>>,
) {
events.iter().for_each(|event| {
// Cleanup unused buttons
root_children.iter().for_each(|children| {
// Iterate over UI children elements
children
.iter()
// Filter to just the buttons we want to despawn
.filter(|&child| {
// Check each child entity against the currently loaded animations
// Note: !(_.any(...)) (not-any)
!((*current).animations.iter().any(|(_, handle)| {
// If the current entitie's animation button corresponds to one of
// the animations loaded, continue, otherwise
existing.get(*child).map_or(false, |scene_button| {
*scene_button == AnimationButton(handle.cast_weak::<AnimationClip>())
})
}))
})
.for_each(|&e| {
commands.entity(e).despawn_recursive();
});
});
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
let (name, _) = gltfs
.get(&current.gltf)
.expect("Load current gltf")
.named_animations
.iter()
.find(|(_, h)| h == &handle)
.expect("Find this animation");
commands.entity(root.single()).with_children(|parent| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
AnimationButton(handle.clone()),
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: name.clone(),
style: TextStyle {
color: Color::WHITE,
..default()
},
}],
..default()
},
..default()
});
});
});
}
AssetEvent::Removed { .. } => warn!("Ignoring asset removal"),
}
});
}
fn control_animation(
interactions: Query<(&Interaction, &AnimationButton), (Changed<Interaction>, With<Button>)>,
) {
interactions
.iter()
.for_each(|(interaction, AnimationButton(handle))| match interaction {
Interaction::Pressed => {
info!("TODO: Play animation {:?}", handle);
}
_ => (),
})
}
/// Wire up the camera UI
fn wire_up_cameras(
added_cam: Query<(Entity, &Name), (Without<MainCamera>, Added<Camera>)>,
mut removed_cam: RemovedComponents<Camera>,
mut commands: Commands,
root: Query<Entity, With<CameraSelectUi>>,
root_children: Query<&Children, With<CameraSelectUi>>,
existing: Query<&CameraButton, Without<UiTitle>>,
) {
// Add new camera buttons
added_cam.iter().for_each(|(entity, name)| {
info!("Adding camera button");
commands.entity(root.single()).with_children(|parent| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
CameraButton(entity),
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: name.as_str().into(),
style: TextStyle {
color: Color::WHITE,
..default()
},
}],
..default()
},
..default()
});
});
});
});
// Remove deleted cameras
removed_cam.iter().for_each(|entity| {
info!("Removing camera button");
root_children
.single()
.iter()
.filter(|&child| {
if let Ok(button) = existing.get(*child) {
*button == CameraButton(entity)
} else {
false
}
})
.for_each(|child| commands.entity(*child).despawn_recursive());
})
}
fn control_camera(
interactions: Query<(&Interaction, &CameraButton), (Changed<Interaction>, With<Button>)>,
mut cameras: Query<(Entity, &mut Camera), Without<MainCamera>>,
) {
interactions
.iter()
.for_each(|(interaction, CameraButton(entity))| match interaction {
Interaction::Pressed => cameras
.iter_mut()
.for_each(|(e, mut camera)| camera.is_active = e == *entity),
_ => (),
})
}
fn control_default_camera(
mut main_camera: Query<&mut Camera, With<MainCamera>>,
other_cameras: Query<&mut Camera, Without<MainCamera>>,
) {
// PERF: Probably don't need to do this every frame
main_camera.single_mut().is_active = other_cameras.is_empty();
}
/// Load gltfs and spawn default scene
fn loading(
mut events: EventReader<AssetEvent<Gltf>>,
gltfs: Res<Assets<Gltf>>,
mut spawn: EventWriter<SpawnScene>,
mut current: ResMut<Current>,
) {
events.iter().for_each(|event| {
info!("Event!: {:?}", event);
if let AssetEvent::<Gltf>::Created { handle, .. } = event {
// Despawn existing scene
let gltf = gltfs.get(handle).expect("Loaded Gltf");
let default_scene = gltf.default_scene.clone().expect("Default scene");
spawn.send(SpawnScene(default_scene));
// Save active scenes
current.scenes = gltf.named_scenes.clone();
current.animations = gltf.named_animations.clone();
// TOOD: Spawn default camera
// TODO: Handle multiple cameras
// TODO: Scene Selection scenes in one Gltf
// TODO: Handle animation
}
});
}
/// Reset the current scene
fn reset_scene(
mut events: EventReader<ResetScene>,
mut commands: Commands,
current: Query<Entity, With<SceneMarker>>,
) {
events.iter().for_each(|_| {
info!("Reset scene");
current
.iter()
.for_each(|e| commands.entity(e).despawn_recursive())
});
}
/// Spawn a desired scene
fn spawn_scene(
mut events: EventReader<SpawnScene>,
mut commands: Commands,
current: Query<Entity, With<SceneMarker>>,
) {
// Handle SpawnScene events
events.iter().for_each(|SpawnScene(handle)| {
info!("Reset scene (inline)");
current
.iter()
.for_each(|e| commands.entity(e).despawn_recursive());
info!("Spawning scene {:?}", handle);
commands.spawn((
SceneBundle {
scene: handle.clone(),
..default()
},
SceneMarker,
));
});
}