Got some semblence of item selection

I have a few options for "next steps" including:
* Figure out bounding boxes using meshes
* Move it into a plugin for re-usability
* Build scene from meshes/nodes for more flexibility
* Move on to something completely different (audio inspector)

I'm inclined to go to audio inspector and then come back to the
mesh/node scene builder.
main
Elijah Voigt 2 years ago
parent d1a7a7f73d
commit 3545aa227c

778
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -29,4 +29,13 @@ path = "bin/debug-info.rs"
[dependencies] [dependencies]
bevy = "0.10" bevy = "0.11"
bevy_rapier3d = "*"
# From rapier docs
[profile.dev.package.bevy_rapier3d]
opt-level = 3
# From rapier docs
[profile.release]
codegen-units = 1

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,7 @@
- [x] basic gltf inspector
- [x] with animation previews
- [ ] inspect specific models
- [x] basic text inspector
- [x] with simple text animation
- [ ] audio inspector
- [x] debug info (FPS)

@ -1,6 +1,6 @@
use bevy::{ use bevy::{
core_pipeline::clear_color::ClearColorConfig, core_pipeline::clear_color::ClearColorConfig,
gltf::Gltf, gltf::{Gltf, GltfNode},
input::{ input::{
keyboard::KeyboardInput, keyboard::KeyboardInput,
mouse::{MouseMotion, MouseWheel}, mouse::{MouseMotion, MouseWheel},
@ -13,32 +13,47 @@ use bevy::{
render_resource::{ render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
}, },
view::VisibleEntities,
}, },
utils::HashSet, window::PrimaryWindow,
}; };
use bevy_rapier3d::{prelude::*, rapier::prelude::RigidBodyType};
use monologue_trees::{debug::*, text::*};
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
title: "GLTF Inspector".into(), title: "GLTF Inspector".into(),
resolution: (640., 480.).into(), resolution: (640., 480.).into(),
..default() ..default()
}), }),
..default() ..default()
})) }),
DebugInfoPlugin,
RapierPhysicsPlugin::<NoUserData>::default(),
RapierDebugRenderPlugin::default(),
))
.add_event::<ManageActive>() .add_event::<ManageActive>()
.add_startup_system(load_models) .add_event::<Selected>()
.add_startup_system(spawn_base_scene) .add_systems(PreStartup, load_models)
.add_startup_system(spawn_base_ui) .add_systems(Startup, (spawn_base_scene, spawn_base_ui))
.add_system(spawn_models) .add_systems(
.add_system(spawn_ui) Update,
.add_system(control_animation) (
.add_system(rotate_model) spawn_models,
.add_system(zoom_model) spawn_ui,
.add_system(scroll) control_animation,
.add_system(select) rotate_model,
.add_system(manage_active) zoom_model,
scroll,
select,
manage_active,
inspect_nodes,
selection,
),
)
.run(); .run();
} }
@ -74,9 +89,17 @@ struct Active;
/// ///
/// Event for managing which entities are tagged "Active" /// Event for managing which entities are tagged "Active"
#[derive(Debug)] #[derive(Event, Debug)]
struct ManageActive(Option<Entity>); struct ManageActive(Option<Entity>);
///
/// Event for tracking which entities are selected
#[derive(Event, Debug)]
enum Selected {
Hovered(Entity),
Selected(Entity),
}
/// ///
/// Load all GLTF models on startup /// Load all GLTF models on startup
fn load_models(mut commands: Commands, ass: Res<AssetServer>) { fn load_models(mut commands: Commands, ass: Res<AssetServer>) {
@ -110,8 +133,9 @@ fn spawn_base_ui(mut commands: Commands) {
NodeBundle { NodeBundle {
style: Style { style: Style {
justify_content: JustifyContent::Center, justify_content: JustifyContent::Center,
size: Size::all(Val::Percent(90.0)), width: Val::Percent(90.0),
overflow: Overflow::Hidden, height: Val::Percent(90.0),
overflow: Overflow::clip(),
..default() ..default()
}, },
..default() ..default()
@ -125,8 +149,8 @@ fn spawn_base_ui(mut commands: Commands) {
flex_wrap: FlexWrap::Wrap, flex_wrap: FlexWrap::Wrap,
flex_direction: FlexDirection::Row, flex_direction: FlexDirection::Row,
justify_content: JustifyContent::SpaceAround, justify_content: JustifyContent::SpaceAround,
size: Size::AUTO, width: Val::Auto,
max_size: Size::UNDEFINED, height: Val::Auto,
..default() ..default()
}, },
..default() ..default()
@ -271,6 +295,8 @@ fn spawn_models(
scene: handle.clone(), scene: handle.clone(),
..default() ..default()
}, },
RigidBody::KinematicPositionBased,
Collider::cylinder(1.0, 1.0),
Inspect, Inspect,
)); ));
}); });
@ -306,7 +332,8 @@ fn spawn_ui(
.with_children(|parent| { .with_children(|parent| {
parent.spawn(ImageBundle { parent.spawn(ImageBundle {
style: Style { style: Style {
size: Size::all(Val::Px(256.0)), width: Val::Px(256.0),
height: Val::Px(256.0),
padding: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)),
..default() ..default()
}, },
@ -322,6 +349,15 @@ fn spawn_ui(
} }
} }
fn inspect_nodes(gltfs: Res<Assets<Gltf>>, nodes: Res<Assets<GltfNode>>, mut done: Local<bool>) {
if !(*done) {
for gltf in gltfs.iter() {
// info!("Gltf {:#?}", gltf);
*done = true;
}
}
}
fn control_animation( fn control_animation(
mut key_evr: EventReader<KeyboardInput>, mut key_evr: EventReader<KeyboardInput>,
mut active_evr: EventReader<ManageActive>, mut active_evr: EventReader<ManageActive>,
@ -425,7 +461,7 @@ fn zoom_model(
mut wheel_evr: EventReader<MouseWheel>, mut wheel_evr: EventReader<MouseWheel>,
mut transforms: Query<&mut Transform, (With<Inspect>, With<Active>, Without<Camera>)>, mut transforms: Query<&mut Transform, (With<Inspect>, With<Active>, Without<Camera>)>,
) { ) {
if keys.pressed(KeyCode::LShift) { if keys.pressed(KeyCode::ShiftLeft) {
for ev in wheel_evr.iter() { for ev in wheel_evr.iter() {
for mut transform in transforms.iter_mut() { for mut transform in transforms.iter_mut() {
let scale = (Vec3::ONE * ev.y) / 100.0; let scale = (Vec3::ONE * ev.y) / 100.0;
@ -444,7 +480,7 @@ fn scroll(
// Only scroll if scene not selected // Only scroll if scene not selected
if active.is_empty() { if active.is_empty() {
for mut s in query.iter_mut() { for mut s in query.iter_mut() {
s.position.top = match s.position.top { s.top = match s.top {
Val::Px(current) => Val::Px(current + (ev.y * 5.0)), Val::Px(current) => Val::Px(current + (ev.y * 5.0)),
_ => Val::Px(0.0), _ => Val::Px(0.0),
}; };
@ -471,7 +507,7 @@ fn select(
mut events: EventWriter<ManageActive>, mut events: EventWriter<ManageActive>,
) { ) {
for (interaction, selected_preview) in query.iter() { for (interaction, selected_preview) in query.iter() {
if interaction == &Interaction::Clicked { if interaction == &Interaction::Pressed {
// Hide UI // Hide UI
let mut ui_vis = selection_ui.single_mut(); let mut ui_vis = selection_ui.single_mut();
*ui_vis = Visibility::Hidden; *ui_vis = Visibility::Hidden;
@ -537,25 +573,23 @@ fn manage_active(
mut events: EventReader<ManageActive>, mut events: EventReader<ManageActive>,
query: Query<&Children>, query: Query<&Children>,
current: Query<Entity, With<Active>>, current: Query<Entity, With<Active>>,
names: Query<&Name>,
) { ) {
for event in events.iter() { for ManageActive(inner) in events.iter() {
info!("Setting active: {:?}", event); info!("Setting active: {:?}", inner);
match event { match inner {
ManageActive(None) => { None => {
for entity in current.iter() { for entity in current.iter() {
if let Some(mut entity_commands) = commands.get_entity(entity) { if let Some(mut entity_commands) = commands.get_entity(entity) {
entity_commands.remove::<Active>(); entity_commands.remove::<Active>();
entity_commands.remove::<ColliderDebugColor>();
} }
} }
} }
ManageActive(Some(entity)) => { Some(entity) => {
for child in query.iter_descendants(*entity) { for child in query.iter_descendants(*entity) {
if let Some(mut child_commands) = commands.get_entity(child) { if let Some(mut child_commands) = commands.get_entity(child) {
// info!( // info!("Name: {:?}", names.get(child));
// "Active entity components: {:?} {:?}",
// child,
// names.get(child)
// );
// child_commands.log_components(); // child_commands.log_components();
child_commands.insert(Active); child_commands.insert(Active);
} }
@ -564,3 +598,36 @@ fn manage_active(
} }
} }
} }
fn selection(
mut commands: Commands,
windows: Query<&Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>,
camera_q: Query<(&Camera, &GlobalTransform, &VisibleEntities), With<Active>>,
rapier_context: Res<RapierContext>,
) {
if !mouse_events.is_empty() {
let window = windows.single();
for _ in mouse_events.iter() {
if let Some(cursor) = window.cursor_position() {
for (camera, camera_t, visible_entities) in camera_q.iter() {
if let Some(ray) = camera.viewport_to_world(camera_t, cursor) {
if let Some((entity, toi)) = rapier_context.cast_ray(
ray.origin,
ray.direction,
f32::MAX,
true,
QueryFilter::new()
.predicate(&|entity| visible_entities.entities.contains(&entity)),
) {
if let Some(mut cmds) = commands.get_entity(entity) {
info!("hit! ({:?}@{})", entity, toi);
cmds.insert(ColliderDebugColor(Color::BLUE));
}
}
}
}
}
}
}
}

@ -1,5 +1,3 @@
use std::time::Duration;
use bevy::prelude::*; use bevy::prelude::*;
use monologue_trees::{debug::*, text::*}; use monologue_trees::{debug::*, text::*};
@ -14,23 +12,30 @@ const LOREM: [&str; 5] = [
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
title: "Text Inspect".into(), title: "Text Inspect".into(),
resolution: (640., 480.).into(), resolution: (640., 480.).into(),
..default() ..default()
}), }),
..default() ..default()
})) }),
.add_plugin(DebugInfoPlugin) DebugInfoPlugin,
.add_plugin(AnimatedTextPlugin) AnimatedTextPlugin,
.add_startup_system(load_fonts) ))
.add_startup_system(init_ui) .add_systems(PreStartup, load_fonts)
.add_system(manage_buttons) .add_systems(Startup, init_ui)
.add_system(manage_animation_button) .add_systems(
.add_system(manage_fonts) Update,
.add_system(mouse_cursor) (
.add_system(manage_animation) manage_buttons,
manage_animation_button,
manage_fonts,
mouse_cursor,
manage_animation,
),
)
.run(); .run();
} }
@ -65,7 +70,8 @@ fn init_ui(mut commands: Commands) {
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {
style: Style { style: Style {
size: Size::all(Val::Percent(100.0)), width: Val::Percent(100.0),
height: Val::Percent(100.0),
..default() ..default()
}, },
background_color: BackgroundColor(Color::BLACK), background_color: BackgroundColor(Color::BLACK),
@ -77,10 +83,7 @@ fn init_ui(mut commands: Commands) {
style: Style { style: Style {
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
align_items: AlignItems::Center, align_items: AlignItems::Center,
size: Size {
width: Val::Px(200.0), width: Val::Px(200.0),
height: Val::Undefined,
},
align_content: AlignContent::SpaceEvenly, align_content: AlignContent::SpaceEvenly,
..default() ..default()
}, },
@ -108,14 +111,11 @@ fn init_ui(mut commands: Commands) {
..default() ..default()
}, },
style: Style { style: Style {
size: Size {
width: Val::Px(400.0), width: Val::Px(400.0),
..default() ..default()
}, },
..default() ..default()
}, },
..default()
},
animated_text: AnimatedText::new(TextAnimationType::Typing(12.0)), animated_text: AnimatedText::new(TextAnimationType::Typing(12.0)),
}, },
PreviewText, PreviewText,
@ -127,17 +127,11 @@ fn init_ui(mut commands: Commands) {
style: Style { style: Style {
align_self: AlignSelf::FlexEnd, align_self: AlignSelf::FlexEnd,
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(5.0), bottom: Val::Px(5.0),
left: Val::Px(5.0), left: Val::Px(5.0),
..default()
},
size: Size {
width: Val::Px(200.0), width: Val::Px(200.0),
..default() ..default()
}, },
..default()
},
background_color: BackgroundColor(Color::default().with_a(0.0)), background_color: BackgroundColor(Color::default().with_a(0.0)),
..default() ..default()
}, },
@ -187,8 +181,8 @@ fn manage_buttons(
.split("/") .split("/")
.last() .last()
.expect("Extracting filename") .expect("Extracting filename")
.strip_suffix(".otf") .trim_end_matches(".otf")
.expect("Stripping prefix"); .trim_end_matches(".ttf");
let style = TextStyle { let style = TextStyle {
font: font.clone(), font: font.clone(),
@ -226,7 +220,7 @@ fn manage_fonts(
) { ) {
for (i, f) in interaction.iter() { for (i, f) in interaction.iter() {
match (i, f) { match (i, f) {
(Interaction::Clicked, FontButton(font)) => { (Interaction::Pressed, FontButton(font)) => {
texts.single_mut().sections.iter_mut().for_each(|section| { texts.single_mut().sections.iter_mut().for_each(|section| {
section.style.font = font.clone(); section.style.font = font.clone();
}); });
@ -242,7 +236,7 @@ fn manage_animation_button(
) { ) {
for (i, f) in interaction.iter() { for (i, f) in interaction.iter() {
match (i, f) { match (i, f) {
(Interaction::Clicked, FontButton(font)) => { (Interaction::Pressed, FontButton(font)) => {
animation_button animation_button
.single_mut() .single_mut()
.sections .sections
@ -262,7 +256,7 @@ fn mouse_cursor(
let mut window = windows.single_mut(); let mut window = windows.single_mut();
window.cursor.icon = match interaction { window.cursor.icon = match interaction {
Interaction::Hovered | Interaction::Clicked => CursorIcon::Hand, Interaction::Hovered | Interaction::Pressed => CursorIcon::Hand,
Interaction::None => CursorIcon::Arrow, Interaction::None => CursorIcon::Arrow,
} }
} }
@ -270,15 +264,17 @@ fn mouse_cursor(
fn manage_animation( fn manage_animation(
mut animated_texts: Query<&mut AnimatedText, With<PreviewText>>, mut animated_texts: Query<&mut AnimatedText, With<PreviewText>>,
interactions: Query<&Interaction, (Changed<Interaction>, With<FontButton>)>, interactions: Query<&Interaction, (Changed<Interaction>, With<AnimationButton>)>,
) { ) {
for interaction in interactions.iter() { for interaction in interactions.iter() {
match interaction { match interaction {
Interaction::Clicked => { Interaction::Pressed => {
let mut preview = animated_texts info!("toggling animation");
let mut anim = animated_texts
.get_single_mut() .get_single_mut()
.expect("Loading animated text"); .expect("Loading animated text");
preview.play(); anim.toggle();
} }
_ => (), _ => (),
} }

@ -10,9 +10,8 @@ pub struct DebugInfoPlugin;
impl Plugin for DebugInfoPlugin { impl Plugin for DebugInfoPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_startup_system(init) app.add_systems(Startup, init)
.add_system(toggle) .add_systems(Update, (toggle, update));
.add_system(update);
} }
} }
@ -34,13 +33,10 @@ fn init(mut commands: Commands, server: Res<AssetServer>) {
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
style: Style { style: Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(0.0), top: Val::Px(0.0),
left: Val::Px(0.0), left: Val::Px(0.0),
..default() ..default()
}, },
..default()
},
background_color: BackgroundColor(Color::GRAY), background_color: BackgroundColor(Color::GRAY),
z_index: ZIndex::Global(999), z_index: ZIndex::Global(999),
..default() ..default()

@ -17,7 +17,7 @@ pub struct AnimatedTextPlugin;
impl Plugin for AnimatedTextPlugin { impl Plugin for AnimatedTextPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_system(manage_texts).add_system(animate_texts); app.add_systems(Update, (manage_texts, animate_texts));
} }
} }
@ -59,6 +59,14 @@ impl AnimatedText {
self.animation_status = TextAnimationStatus::Stopped; self.animation_status = TextAnimationStatus::Stopped;
self.animation_duration = None; self.animation_duration = None;
} }
pub fn toggle(&mut self) {
use TextAnimationStatus::*;
self.animation_status = match self.animation_status {
Playing => Stopped,
Stopped => Playing,
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -68,9 +76,9 @@ pub enum TextAnimationType {
#[derive(Default, Debug)] #[derive(Default, Debug)]
enum TextAnimationStatus { enum TextAnimationStatus {
Playing,
#[default] #[default]
Stopped, Stopped,
Playing,
} }
/// Manage individual text entities /// Manage individual text entities
@ -121,22 +129,41 @@ fn animate_texts(mut query: Query<(&mut Text, &mut AnimatedText)>, time: Res<Tim
Some(TextAnimationType::Typing(seconds)) => { Some(TextAnimationType::Typing(seconds)) => {
animated_text.animation_duration = match animated_text.animation_duration { animated_text.animation_duration = match animated_text.animation_duration {
None | Some(Duration::ZERO) => { None | Some(Duration::ZERO) => {
// Set sections to alpha 0 before animation begins
text.sections.iter_mut().for_each(|section| {
section.style.color.set_a(0.0);
});
// We have just (re)started the animation, so set the duration to full // We have just (re)started the animation, so set the duration to full
Some(Duration::from_secs_f32(seconds)) Some(Duration::from_secs_f32(seconds))
} }
// FIXME: Why can't mutate inner on animation_duration? // FIXME: Why can't mutate inner on animation_duration?
Some(inner) => { Some(inner) => {
{ {
let percentage = 1.0 - (inner.as_secs_f32()) - seconds; // how far into the animation are we?
let percentage = 1.0 - (inner.as_secs_f32() / seconds);
let len_total = text
.sections // Find the total number of characters to be processed
.iter() // let len_total = text
.fold(0, |acc, curr| acc + curr.value.len()); // .sections
// .iter()
// .fold(0, |acc, curr| acc + curr.value.len());
// Backup version:
let len_total = text.sections.len();
// Find the farthest character into the string to show
let target = (len_total as f32 * percentage) as usize; let target = (len_total as f32 * percentage) as usize;
for mut section in text.sections.iter_mut() {} // Assign all segments an alpha of 0 or 1.
// TODO: Incremental updates only: Start at target, work backward
// until you get to one with alpha 1 and then break
text.sections
.iter_mut()
.take(target)
.rev()
.take_while(|section| section.style.color.a() != 1.0)
.for_each(|section| {
section.style.color.set_a(1.0);
});
} }
// We are continuing the animation, so decrement the remaining duration // We are continuing the animation, so decrement the remaining duration
@ -148,51 +175,3 @@ fn animate_texts(mut query: Query<(&mut Text, &mut AnimatedText)>, time: Res<Tim
} }
} }
} }
// PERF: Why does this slow down immediatly?
// if *duration > Duration::ZERO {
// *duration = duration.saturating_sub(time.delta());
//
// let percentage = 1.0 - (duration.as_secs_f32() / 30.0);
//
// let mut text = texts.single_mut();
//
// let len_total = desired.iter().fold(0, |acc, curr| acc + curr.len());
// let mut len_total = ((len_total as f32 * percentage) as usize).min(len_total as usize);
//
// for (section, desired) in text.sections.iter_mut().zip(desired.iter()) {
// // This section is empty, initialize with capacity
// if len_total == 0 {
// break;
//
// // Total amount of remaining text is greater than this section
// // Set this text block to the full section
// } else if len_total >= desirted.len() {
// // info!("len_total >= desired.len()");
// len_total -= desired.len();
// if section.value.len() != desired.len() {
// // info!("value != desired");
// section.value = desired.clone();
// }
//
// // Total remaining text is less than this section
// // Set to a sub-string of text
// } else if len_total < desired.len() {
// // info!("len total < desired len");
// // Difference between current value and desired length
// let diff = desired
// .split_at(section.value.len())
// .1
// .split_at(len_total - section.value.len())
// .0
// .into();
// // info!("adding value {}", diff);
// section.value.push_str(diff);
// len_total = 0;
//
// // Undefined behavior
// } else {
// info!("Unexpected text animation situation");
// }
// }
// }

14
tmp

@ -0,0 +1,14 @@
if *duration > Duration::ZERO {
let mut text = texts.single_mut();
let total_sections = text.sections.len();
*duration = duration.saturating_sub(time.delta());
for (idx, section) in text.sections.iter_mut().enumerate() {
let ratio = ((idx + 1) as f32) / (total_sections as f32);
let cursor = 1.0 - ((*duration).as_secs_f32() / 30.0);
let alpha = if cursor > ratio { 1.0 } else { 0.0 };
section.style.color.set_a(alpha);
}
}
Loading…
Cancel
Save