gltf inspector 0.2git add .; git commit

main
Elijah Voigt 2 years ago
parent 3f37d0b601
commit a2c3ceade3

@ -1,4 +1,4 @@
use bevy::{asset::AssetEvents, gltf::Gltf, prelude::*, utils::HashMap};
use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use monologue_trees::debug::*;
fn main() {
@ -18,6 +18,7 @@ fn main() {
.add_event::<SpawnScene>()
.init_resource::<Current>()
.add_systems(Startup, spawn_ui)
.add_systems(PreUpdate, (add_camera_ui, add_scene_ui, add_animation_ui))
.add_systems(
Update,
(
@ -25,13 +26,19 @@ fn main() {
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,
control_default_light,
),
)
.add_systems(
PostUpdate,
(
clean_ui::<SceneButton>,
clean_ui::<AnimationButton>,
clean_ui::<CameraButton>,
),
)
.run();
@ -41,7 +48,10 @@ fn main() {
struct SceneMarker;
#[derive(Component)]
struct MainCamera;
struct DefaultCamera;
#[derive(Component)]
struct DefaultLight;
#[derive(Component)]
struct MainUi;
@ -58,6 +68,9 @@ struct SceneSelectUi;
#[derive(Component, PartialEq, Debug)]
struct SceneButton(Handle<Scene>);
#[derive(Event)]
struct SpawnScene(Handle<Scene>);
#[derive(Component)]
struct AnimationSelectUi;
@ -80,8 +93,8 @@ struct Current {
#[derive(Event)]
struct ResetScene;
#[derive(Event)]
struct SpawnScene(Handle<Scene>);
#[derive(Component)]
struct ResetAnimation;
fn drag_and_drop(
mut events: EventReader<FileDragAndDrop>,
@ -90,13 +103,9 @@ fn drag_and_drop(
) {
events
.iter()
.filter_map(|event| {
if let FileDragAndDrop::DroppedFile { path_buf, .. } = event {
info!("Drag+Drop file: {:?}", path_buf);
Some(path_buf)
} else {
None
}
.filter_map(|event| match event {
FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf),
_ => None,
})
.map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str")))
.for_each(|handle| current.gltf = handle.clone());
@ -107,9 +116,19 @@ fn spawn_ui(mut commands: Commands) {
// TODO: Scene select container
// TODO: Animation Play/Pause Placeholder
commands.spawn((
Camera3dBundle { ..default() },
Camera3dBundle {
transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
UiCameraConfig { show_ui: true },
MainCamera,
DefaultCamera,
));
commands.spawn((
DirectionalLightBundle {
transform: Transform::default().looking_at(Vec3::new(1.0, -1.0, -1.0), Vec3::Y),
..default()
},
DefaultLight,
));
commands
.spawn((
@ -127,23 +146,46 @@ fn spawn_ui(mut commands: Commands) {
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()
},
}],
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
InstructionsUi,
));
})
.with_children(|parent| {
parent.spawn((
TextBundle::from_section(
"Drag and Drop .gltf/.glb file",
TextStyle {
color: Color::WHITE,
..default()
},
),
InstructionsUi,
));
parent.spawn((
TextBundle::from_section(
"Using default camera",
TextStyle {
color: Color::WHITE,
..default()
},
),
DefaultCamera,
));
parent.spawn((
TextBundle::from_section(
"Using default light",
TextStyle {
color: Color::WHITE,
..default()
},
),
DefaultLight,
));
});
parent
.spawn((
NodeBundle {
@ -170,11 +212,11 @@ fn spawn_ui(mut commands: Commands) {
},
..default()
},
AnimationSelectUi,
CameraSelectUi,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section("Animations", TextStyle { ..default() }),
TextBundle::from_section("Cameras", TextStyle { ..default() }),
UiTitle,
));
});
@ -187,91 +229,35 @@ fn spawn_ui(mut commands: Commands) {
},
..default()
},
CameraSelectUi,
AnimationSelectUi,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section("Cameras", TextStyle { ..default() }),
TextBundle::from_section("Animations", TextStyle { ..default() }),
UiTitle,
));
});
});
}
fn wire_up_scenes(
mut events: EventReader<AssetEvent<Scene>>,
root: Query<Entity, With<SceneSelectUi>>,
root_children: Query<&Children, With<SceneSelectUi>>,
/// When a new camera is loaded, clear the camera buttons
fn clean_ui<T: Component>(
mut spawn_events: EventReader<SpawnScene>,
mut clear_events: EventReader<ResetScene>,
cameras: Query<Entity, With<T>>,
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();
});
// We don't care about the content of these events, just that we want to clear the UI after
// each spawn or reset event, so we map each event to () and chain the two event streams.
spawn_events
.iter()
.map(|_| ())
.chain(clear_events.iter().map(|_| ()))
.for_each(|_| {
cameras.iter().for_each(|entity| {
commands.entity(entity).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
@ -288,150 +274,88 @@ fn control_scene(
});
}
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>>,
/// Add scene buttons when a new scene is added/gltf is loaded
fn add_scene_ui(
mut events: EventReader<SpawnScene>,
current: Res<Current>,
existing: Query<&AnimationButton, Without<UiTitle>>,
root: Query<Entity, With<SceneSelectUi>>,
mut commands: Commands,
) {
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();
});
events.iter().for_each(|_| {
commands.entity(root.single()).with_children(|parent| {
current.scenes.iter().for_each(|(name, handle)| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
SceneButton(handle.clone()),
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
});
});
});
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>)>,
mut players: Query<(&mut AnimationPlayer, &Name)>,
clips: Res<Assets<AnimationClip>>,
) {
// For each button interaction
interactions
.iter()
.for_each(|(interaction, AnimationButton(handle))| match interaction {
Interaction::Pressed => {
info!("TODO: Play animation {:?}", handle);
}
// If this is a butotn press
Interaction::Pressed => players
.iter_mut()
// Find all entities compatible with this animation
.filter(|(_, name)| {
clips
.get(handle)
.expect("Check animation clip compatability")
.compatible_with(name)
})
// Play the given (checked compatible) animation
.for_each(|(mut player, _)| {
player.play(handle.clone()).repeat();
}),
_ => (),
})
}
/// Wire up the camera UI
fn wire_up_cameras(
added_cam: Query<(Entity, &Name), (Without<MainCamera>, Added<Camera>)>,
mut removed_cam: RemovedComponents<Camera>,
/// Add animation buttons when a new animation is added/gltf is loaded
fn add_animation_ui(
mut events: EventReader<SpawnScene>,
current: Res<Current>,
root: Query<Entity, With<AnimationSelectUi>>,
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");
events.iter().for_each(|_| {
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()
},
}],
current.animations.iter().for_each(|(name, handle)| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
..default()
AnimationButton(handle.clone()),
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(name, TextStyle { ..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>>,
mut cameras: Query<(Entity, &mut Camera), Without<DefaultCamera>>,
) {
interactions
.iter()
@ -443,12 +367,70 @@ fn control_camera(
})
}
/// If there are no other cameras, use default camera
fn control_default_camera(
mut main_camera: Query<&mut Camera, With<MainCamera>>,
other_cameras: Query<&mut Camera, Without<MainCamera>>,
mut main_camera: Query<&mut Camera, With<DefaultCamera>>,
other_cameras: Query<Entity, (With<Camera>, Without<DefaultCamera>)>,
mut text_indicators: Query<&mut Visibility, With<DefaultCamera>>,
) {
// PERF: Probably don't need to do this every frame
main_camera.single_mut().is_active = other_cameras.is_empty();
// Figure out if we should use default camera
let state = other_cameras.is_empty();
// Toggle camera
main_camera
.iter_mut()
.for_each(|mut cam| cam.is_active = state);
// Update UI indicator
text_indicators.iter_mut().for_each(|mut vis| {
*vis = match state {
true => Visibility::Visible,
false => Visibility::Hidden,
}
})
}
/// If there are no other lights, use default light
fn control_default_light(
mut toggle: Query<&mut Visibility, With<DefaultLight>>,
other_lights: Query<
Entity,
(
Or<(With<SpotLight>, With<DirectionalLight>, With<PointLight>)>,
Without<DefaultLight>,
),
>,
) {
toggle.iter_mut().for_each(|mut vis| {
*vis = if other_lights.is_empty() {
Visibility::Visible
} else {
Visibility::Hidden
}
});
}
/// Add camera buttons when a new camera is added/gltf is loaded
fn add_camera_ui(
events: Query<(Entity, &Name), Added<Camera>>,
ui_root: Query<Entity, With<CameraSelectUi>>,
mut commands: Commands,
) {
events.iter().for_each(|(entity, name)| {
commands.entity(ui_root.single()).with_children(|parent| {
parent
.spawn((
ButtonBundle {
background_color: BackgroundColor(Color::NONE),
..default()
},
CameraButton(entity),
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
});
});
})
}
/// Load gltfs and spawn default scene
@ -459,21 +441,19 @@ fn loading(
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
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
let gltf = gltfs.get(handle).expect("Loaded Gltf");
// Save active scenes
current.scenes = gltf.named_scenes.clone();
current.animations = gltf.named_animations.clone();
// Despawn existing scene
let default_scene = gltf.default_scene.clone().expect("Default scene");
spawn.send(SpawnScene(default_scene));
}
AssetEvent::Removed { .. } => warn!("Ignoring asset removal"),
}
});
}

Loading…
Cancel
Save