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::*; use monologue_trees::debug::*;
fn main() { fn main() {
@ -18,6 +18,7 @@ fn main() {
.add_event::<SpawnScene>() .add_event::<SpawnScene>()
.init_resource::<Current>() .init_resource::<Current>()
.add_systems(Startup, spawn_ui) .add_systems(Startup, spawn_ui)
.add_systems(PreUpdate, (add_camera_ui, add_scene_ui, add_animation_ui))
.add_systems( .add_systems(
Update, Update,
( (
@ -25,13 +26,19 @@ fn main() {
loading, loading,
reset_scene.before(spawn_scene), reset_scene.before(spawn_scene),
spawn_scene.after(reset_scene), spawn_scene.after(reset_scene),
wire_up_scenes,
control_scene, control_scene,
wire_up_animations,
control_animation, control_animation,
wire_up_cameras,
control_camera, control_camera,
control_default_camera, control_default_camera,
control_default_light,
),
)
.add_systems(
PostUpdate,
(
clean_ui::<SceneButton>,
clean_ui::<AnimationButton>,
clean_ui::<CameraButton>,
), ),
) )
.run(); .run();
@ -41,7 +48,10 @@ fn main() {
struct SceneMarker; struct SceneMarker;
#[derive(Component)] #[derive(Component)]
struct MainCamera; struct DefaultCamera;
#[derive(Component)]
struct DefaultLight;
#[derive(Component)] #[derive(Component)]
struct MainUi; struct MainUi;
@ -58,6 +68,9 @@ struct SceneSelectUi;
#[derive(Component, PartialEq, Debug)] #[derive(Component, PartialEq, Debug)]
struct SceneButton(Handle<Scene>); struct SceneButton(Handle<Scene>);
#[derive(Event)]
struct SpawnScene(Handle<Scene>);
#[derive(Component)] #[derive(Component)]
struct AnimationSelectUi; struct AnimationSelectUi;
@ -80,8 +93,8 @@ struct Current {
#[derive(Event)] #[derive(Event)]
struct ResetScene; struct ResetScene;
#[derive(Event)] #[derive(Component)]
struct SpawnScene(Handle<Scene>); struct ResetAnimation;
fn drag_and_drop( fn drag_and_drop(
mut events: EventReader<FileDragAndDrop>, mut events: EventReader<FileDragAndDrop>,
@ -90,13 +103,9 @@ fn drag_and_drop(
) { ) {
events events
.iter() .iter()
.filter_map(|event| { .filter_map(|event| match event {
if let FileDragAndDrop::DroppedFile { path_buf, .. } = event { FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf),
info!("Drag+Drop file: {:?}", path_buf); _ => None,
Some(path_buf)
} else {
None
}
}) })
.map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str"))) .map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str")))
.for_each(|handle| current.gltf = handle.clone()); .for_each(|handle| current.gltf = handle.clone());
@ -107,9 +116,19 @@ fn spawn_ui(mut commands: Commands) {
// TODO: Scene select container // TODO: Scene select container
// TODO: Animation Play/Pause Placeholder // TODO: Animation Play/Pause Placeholder
commands.spawn(( 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 }, 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 commands
.spawn(( .spawn((
@ -127,23 +146,46 @@ fn spawn_ui(mut commands: Commands) {
MainUi, MainUi,
)) ))
.with_children(|parent| { .with_children(|parent| {
// TOOD: Prompt to drag+drop Gltf/Gltf file parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
})
.with_children(|parent| {
parent.spawn(( parent.spawn((
TextBundle { TextBundle::from_section(
text: Text { "Drag and Drop .gltf/.glb file",
sections: vec![TextSection { TextStyle {
value: String::from("Drag and Drop .gltf/.glb file"),
style: TextStyle {
color: Color::WHITE, color: Color::WHITE,
..default() ..default()
}, },
}], ),
InstructionsUi,
));
parent.spawn((
TextBundle::from_section(
"Using default camera",
TextStyle {
color: Color::WHITE,
..default() ..default()
}, },
),
DefaultCamera,
));
parent.spawn((
TextBundle::from_section(
"Using default light",
TextStyle {
color: Color::WHITE,
..default() ..default()
}, },
InstructionsUi, ),
DefaultLight,
)); ));
});
parent parent
.spawn(( .spawn((
NodeBundle { NodeBundle {
@ -170,11 +212,11 @@ fn spawn_ui(mut commands: Commands) {
}, },
..default() ..default()
}, },
AnimationSelectUi, CameraSelectUi,
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
TextBundle::from_section("Animations", TextStyle { ..default() }), TextBundle::from_section("Cameras", TextStyle { ..default() }),
UiTitle, UiTitle,
)); ));
}); });
@ -187,92 +229,36 @@ fn spawn_ui(mut commands: Commands) {
}, },
..default() ..default()
}, },
CameraSelectUi, AnimationSelectUi,
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
TextBundle::from_section("Cameras", TextStyle { ..default() }), TextBundle::from_section("Animations", TextStyle { ..default() }),
UiTitle, UiTitle,
)); ));
}); });
}); });
} }
fn wire_up_scenes( /// When a new camera is loaded, clear the camera buttons
mut events: EventReader<AssetEvent<Scene>>, fn clean_ui<T: Component>(
root: Query<Entity, With<SceneSelectUi>>, mut spawn_events: EventReader<SpawnScene>,
root_children: Query<&Children, With<SceneSelectUi>>, mut clear_events: EventReader<ResetScene>,
cameras: Query<Entity, With<T>>,
mut commands: Commands, mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
current: Res<Current>,
existing: Query<&SceneButton, Without<UiTitle>>,
) { ) {
events.iter().for_each(|event| { // We don't care about the content of these events, just that we want to clear the UI after
// Cleanup unused buttons // each spawn or reset event, so we map each event to () and chain the two event streams.
root_children.iter().for_each(|children| { spawn_events
// Iterate over UI children elements
children
.iter() .iter()
// Filter to just the buttons we want to despawn .map(|_| ())
.filter(|&child| { .chain(clear_events.iter().map(|_| ()))
// Check each child entity against the currently loaded scenes .for_each(|_| {
// Note: !(_.any(...)) (not-any) cameras.iter().for_each(|entity| {
!((*current).scenes.iter().any(|(_, handle)| { commands.entity(entity).despawn_recursive();
// 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 /// Translate UI button presses into scene spawn events
fn control_scene( fn control_scene(
@ -288,150 +274,88 @@ fn control_scene(
}); });
} }
fn wire_up_animations( /// Add scene buttons when a new scene is added/gltf is loaded
mut events: EventReader<AssetEvent<AnimationClip>>, fn add_scene_ui(
root: Query<Entity, With<AnimationSelectUi>>, mut events: EventReader<SpawnScene>,
root_children: Query<&Children, With<AnimationSelectUi>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
current: Res<Current>, current: Res<Current>,
existing: Query<&AnimationButton, Without<UiTitle>>, root: Query<Entity, With<SceneSelectUi>>,
mut commands: Commands,
) { ) {
events.iter().for_each(|event| { events.iter().for_each(|_| {
// 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| { commands.entity(root.single()).with_children(|parent| {
current.scenes.iter().for_each(|(name, handle)| {
parent parent
.spawn(( .spawn((
ButtonBundle { ButtonBundle {
background_color: BackgroundColor(Color::NONE), background_color: BackgroundColor(Color::NONE),
..default() ..default()
}, },
AnimationButton(handle.clone()), SceneButton(handle.clone()),
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(TextBundle { parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
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( fn control_animation(
interactions: Query<(&Interaction, &AnimationButton), (Changed<Interaction>, With<Button>)>, interactions: Query<(&Interaction, &AnimationButton), (Changed<Interaction>, With<Button>)>,
mut players: Query<(&mut AnimationPlayer, &Name)>,
clips: Res<Assets<AnimationClip>>,
) { ) {
// For each button interaction
interactions interactions
.iter() .iter()
.for_each(|(interaction, AnimationButton(handle))| match interaction { .for_each(|(interaction, AnimationButton(handle))| match interaction {
Interaction::Pressed => { // If this is a butotn press
info!("TODO: Play animation {:?}", handle); 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 /// Add animation buttons when a new animation is added/gltf is loaded
fn wire_up_cameras( fn add_animation_ui(
added_cam: Query<(Entity, &Name), (Without<MainCamera>, Added<Camera>)>, mut events: EventReader<SpawnScene>,
mut removed_cam: RemovedComponents<Camera>, current: Res<Current>,
root: Query<Entity, With<AnimationSelectUi>>,
mut commands: Commands, mut commands: Commands,
root: Query<Entity, With<CameraSelectUi>>,
root_children: Query<&Children, With<CameraSelectUi>>,
existing: Query<&CameraButton, Without<UiTitle>>,
) { ) {
// Add new camera buttons events.iter().for_each(|_| {
added_cam.iter().for_each(|(entity, name)| {
info!("Adding camera button");
commands.entity(root.single()).with_children(|parent| { commands.entity(root.single()).with_children(|parent| {
current.animations.iter().for_each(|(name, handle)| {
parent parent
.spawn(( .spawn((
ButtonBundle { ButtonBundle {
background_color: BackgroundColor(Color::NONE), background_color: BackgroundColor(Color::NONE),
..default() ..default()
}, },
CameraButton(entity), AnimationButton(handle.clone()),
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(TextBundle { parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
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( fn control_camera(
interactions: Query<(&Interaction, &CameraButton), (Changed<Interaction>, With<Button>)>, 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 interactions
.iter() .iter()
@ -443,12 +367,70 @@ fn control_camera(
}) })
} }
/// If there are no other cameras, use default camera
fn control_default_camera( fn control_default_camera(
mut main_camera: Query<&mut Camera, With<MainCamera>>, mut main_camera: Query<&mut Camera, With<DefaultCamera>>,
other_cameras: Query<&mut Camera, Without<MainCamera>>, 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 // Figure out if we should use default camera
main_camera.single_mut().is_active = other_cameras.is_empty(); 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 /// Load gltfs and spawn default scene
@ -459,21 +441,19 @@ fn loading(
mut current: ResMut<Current>, mut current: ResMut<Current>,
) { ) {
events.iter().for_each(|event| { events.iter().for_each(|event| {
info!("Event!: {:?}", event); match event {
if let AssetEvent::<Gltf>::Created { handle, .. } = event { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
// Despawn existing scene
let gltf = gltfs.get(handle).expect("Loaded Gltf"); 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 // Save active scenes
current.scenes = gltf.named_scenes.clone(); current.scenes = gltf.named_scenes.clone();
current.animations = gltf.named_animations.clone(); current.animations = gltf.named_animations.clone();
// TOOD: Spawn default camera // Despawn existing scene
// TODO: Handle multiple cameras let default_scene = gltf.default_scene.clone().expect("Default scene");
// TODO: Scene Selection scenes in one Gltf spawn.send(SpawnScene(default_scene));
// TODO: Handle animation }
AssetEvent::Removed { .. } => warn!("Ignoring asset removal"),
} }
}); });
} }

Loading…
Cancel
Save