mirror of
https://github.com/bevyengine/bevy
synced 2024-12-22 19:13:08 +00:00
015f2c69ca
# Objective Continue improving the user experience of our UI Node API in the direction specified by [Bevy's Next Generation Scene / UI System](https://github.com/bevyengine/bevy/discussions/14437) ## Solution As specified in the document above, merge `Style` fields into `Node`, and move "computed Node fields" into `ComputedNode` (I chose this name over something like `ComputedNodeLayout` because it currently contains more than just layout info. If we want to break this up / rename these concepts, lets do that in a separate PR). `Style` has been removed. This accomplishes a number of goals: ## Ergonomics wins Specifying both `Node` and `Style` is now no longer required for non-default styles Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` ## Conceptual clarity `Style` was never a comprehensive "style sheet". It only defined "core" style properties that all `Nodes` shared. Any "styled property" that couldn't fit that mold had to be in a separate component. A "real" style system would style properties _across_ components (`Node`, `Button`, etc). We have plans to build a true style system (see the doc linked above). By moving the `Style` fields to `Node`, we fully embrace `Node` as the driving concept and remove the "style system" confusion. ## Next Steps * Consider identifying and splitting out "style properties that aren't core to Node". This should not happen for Bevy 0.15. --- ## Migration Guide Move any fields set on `Style` into `Node` and replace all `Style` component usage with `Node`. Before: ```rust commands.spawn(( Node::default(), Style { width: Val::Px(100.), ..default() }, )); ``` After: ```rust commands.spawn(Node { width: Val::Px(100.), ..default() }); ``` For any usage of the "computed node properties" that used to live on `Node`, use `ComputedNode` instead: Before: ```rust fn system(nodes: Query<&Node>) { for node in &nodes { let computed_size = node.size(); } } ``` After: ```rust fn system(computed_nodes: Query<&ComputedNode>) { for computed_node in &computed_nodes { let computed_size = computed_node.size(); } } ```
297 lines
10 KiB
Rust
297 lines
10 KiB
Rust
//! Enable controls for morph targets detected in a loaded scene.
|
|
//!
|
|
//! Collect morph targets and assign keys to them,
|
|
//! shows on screen additional controls for morph targets.
|
|
//!
|
|
//! Illustrates how to access and modify individual morph target weights.
|
|
//! See the [`update_morphs`] system for details.
|
|
//!
|
|
//! Also illustrates how to read morph target names in [`detect_morphs`].
|
|
|
|
use crate::scene_viewer_plugin::SceneHandle;
|
|
use bevy::prelude::*;
|
|
use std::fmt;
|
|
|
|
const WEIGHT_PER_SECOND: f32 = 0.8;
|
|
const ALL_MODIFIERS: &[KeyCode] = &[KeyCode::ShiftLeft, KeyCode::ControlLeft, KeyCode::AltLeft];
|
|
const AVAILABLE_KEYS: [MorphKey; 56] = [
|
|
MorphKey::new("r", &[], KeyCode::KeyR),
|
|
MorphKey::new("t", &[], KeyCode::KeyT),
|
|
MorphKey::new("z", &[], KeyCode::KeyZ),
|
|
MorphKey::new("i", &[], KeyCode::KeyI),
|
|
MorphKey::new("o", &[], KeyCode::KeyO),
|
|
MorphKey::new("p", &[], KeyCode::KeyP),
|
|
MorphKey::new("f", &[], KeyCode::KeyF),
|
|
MorphKey::new("g", &[], KeyCode::KeyG),
|
|
MorphKey::new("h", &[], KeyCode::KeyH),
|
|
MorphKey::new("j", &[], KeyCode::KeyJ),
|
|
MorphKey::new("k", &[], KeyCode::KeyK),
|
|
MorphKey::new("y", &[], KeyCode::KeyY),
|
|
MorphKey::new("x", &[], KeyCode::KeyX),
|
|
MorphKey::new("c", &[], KeyCode::KeyC),
|
|
MorphKey::new("v", &[], KeyCode::KeyV),
|
|
MorphKey::new("b", &[], KeyCode::KeyB),
|
|
MorphKey::new("n", &[], KeyCode::KeyN),
|
|
MorphKey::new("m", &[], KeyCode::KeyM),
|
|
MorphKey::new("0", &[], KeyCode::Digit0),
|
|
MorphKey::new("1", &[], KeyCode::Digit1),
|
|
MorphKey::new("2", &[], KeyCode::Digit2),
|
|
MorphKey::new("3", &[], KeyCode::Digit3),
|
|
MorphKey::new("4", &[], KeyCode::Digit4),
|
|
MorphKey::new("5", &[], KeyCode::Digit5),
|
|
MorphKey::new("6", &[], KeyCode::Digit6),
|
|
MorphKey::new("7", &[], KeyCode::Digit7),
|
|
MorphKey::new("8", &[], KeyCode::Digit8),
|
|
MorphKey::new("9", &[], KeyCode::Digit9),
|
|
MorphKey::new("lshift-R", &[KeyCode::ShiftLeft], KeyCode::KeyR),
|
|
MorphKey::new("lshift-T", &[KeyCode::ShiftLeft], KeyCode::KeyT),
|
|
MorphKey::new("lshift-Z", &[KeyCode::ShiftLeft], KeyCode::KeyZ),
|
|
MorphKey::new("lshift-I", &[KeyCode::ShiftLeft], KeyCode::KeyI),
|
|
MorphKey::new("lshift-O", &[KeyCode::ShiftLeft], KeyCode::KeyO),
|
|
MorphKey::new("lshift-P", &[KeyCode::ShiftLeft], KeyCode::KeyP),
|
|
MorphKey::new("lshift-F", &[KeyCode::ShiftLeft], KeyCode::KeyF),
|
|
MorphKey::new("lshift-G", &[KeyCode::ShiftLeft], KeyCode::KeyG),
|
|
MorphKey::new("lshift-H", &[KeyCode::ShiftLeft], KeyCode::KeyH),
|
|
MorphKey::new("lshift-J", &[KeyCode::ShiftLeft], KeyCode::KeyJ),
|
|
MorphKey::new("lshift-K", &[KeyCode::ShiftLeft], KeyCode::KeyK),
|
|
MorphKey::new("lshift-Y", &[KeyCode::ShiftLeft], KeyCode::KeyY),
|
|
MorphKey::new("lshift-X", &[KeyCode::ShiftLeft], KeyCode::KeyX),
|
|
MorphKey::new("lshift-C", &[KeyCode::ShiftLeft], KeyCode::KeyC),
|
|
MorphKey::new("lshift-V", &[KeyCode::ShiftLeft], KeyCode::KeyV),
|
|
MorphKey::new("lshift-B", &[KeyCode::ShiftLeft], KeyCode::KeyB),
|
|
MorphKey::new("lshift-N", &[KeyCode::ShiftLeft], KeyCode::KeyN),
|
|
MorphKey::new("lshift-M", &[KeyCode::ShiftLeft], KeyCode::KeyM),
|
|
MorphKey::new("lshift-0", &[KeyCode::ShiftLeft], KeyCode::Digit0),
|
|
MorphKey::new("lshift-1", &[KeyCode::ShiftLeft], KeyCode::Digit1),
|
|
MorphKey::new("lshift-2", &[KeyCode::ShiftLeft], KeyCode::Digit2),
|
|
MorphKey::new("lshift-3", &[KeyCode::ShiftLeft], KeyCode::Digit3),
|
|
MorphKey::new("lshift-4", &[KeyCode::ShiftLeft], KeyCode::Digit4),
|
|
MorphKey::new("lshift-5", &[KeyCode::ShiftLeft], KeyCode::Digit5),
|
|
MorphKey::new("lshift-6", &[KeyCode::ShiftLeft], KeyCode::Digit6),
|
|
MorphKey::new("lshift-7", &[KeyCode::ShiftLeft], KeyCode::Digit7),
|
|
MorphKey::new("lshift-8", &[KeyCode::ShiftLeft], KeyCode::Digit8),
|
|
MorphKey::new("lshift-9", &[KeyCode::ShiftLeft], KeyCode::Digit9),
|
|
];
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum WeightChange {
|
|
Increase,
|
|
Decrease,
|
|
}
|
|
impl WeightChange {
|
|
fn reverse(&mut self) {
|
|
*self = match *self {
|
|
WeightChange::Increase => WeightChange::Decrease,
|
|
WeightChange::Decrease => WeightChange::Increase,
|
|
}
|
|
}
|
|
fn sign(self) -> f32 {
|
|
match self {
|
|
WeightChange::Increase => 1.0,
|
|
WeightChange::Decrease => -1.0,
|
|
}
|
|
}
|
|
fn change_weight(&mut self, weight: f32, change: f32) -> f32 {
|
|
let mut change = change * self.sign();
|
|
let new_weight = weight + change;
|
|
if new_weight <= 0.0 || new_weight >= 1.0 {
|
|
self.reverse();
|
|
change = -change;
|
|
}
|
|
weight + change
|
|
}
|
|
}
|
|
|
|
struct Target {
|
|
entity_name: Option<String>,
|
|
entity: Entity,
|
|
name: Option<String>,
|
|
index: usize,
|
|
weight: f32,
|
|
change_dir: WeightChange,
|
|
}
|
|
impl fmt::Display for Target {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match (self.name.as_ref(), self.entity_name.as_ref()) {
|
|
(None, None) => write!(f, "animation{} of {:?}", self.index, self.entity),
|
|
(None, Some(entity)) => write!(f, "animation{} of {entity}", self.index),
|
|
(Some(target), None) => write!(f, "{target} of {:?}", self.entity),
|
|
(Some(target), Some(entity)) => write!(f, "{target} of {entity}"),
|
|
}?;
|
|
write!(f, ": {}", self.weight)
|
|
}
|
|
}
|
|
impl Target {
|
|
fn text_span(&self, key: &str, style: TextFont) -> (String, TextFont) {
|
|
(format!("[{key}] {self}\n"), style)
|
|
}
|
|
fn new(
|
|
entity_name: Option<&Name>,
|
|
weights: &[f32],
|
|
target_names: Option<&[String]>,
|
|
entity: Entity,
|
|
) -> Vec<Target> {
|
|
let get_name = |i| target_names.and_then(|names| names.get(i));
|
|
let entity_name = entity_name.map(Name::as_str);
|
|
weights
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, weight)| Target {
|
|
entity_name: entity_name.map(ToOwned::to_owned),
|
|
entity,
|
|
name: get_name(index).cloned(),
|
|
index,
|
|
weight: *weight,
|
|
change_dir: WeightChange::Increase,
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct WeightsControl {
|
|
weights: Vec<Target>,
|
|
}
|
|
|
|
struct MorphKey {
|
|
name: &'static str,
|
|
modifiers: &'static [KeyCode],
|
|
key: KeyCode,
|
|
}
|
|
impl MorphKey {
|
|
const fn new(name: &'static str, modifiers: &'static [KeyCode], key: KeyCode) -> Self {
|
|
MorphKey {
|
|
name,
|
|
modifiers,
|
|
key,
|
|
}
|
|
}
|
|
fn active(&self, inputs: &ButtonInput<KeyCode>) -> bool {
|
|
let mut modifier = self.modifiers.iter();
|
|
let mut non_modifier = ALL_MODIFIERS.iter().filter(|m| !self.modifiers.contains(m));
|
|
|
|
let key = inputs.pressed(self.key);
|
|
let modifier = modifier.all(|m| inputs.pressed(*m));
|
|
let non_modifier = non_modifier.all(|m| !inputs.pressed(*m));
|
|
key && modifier && non_modifier
|
|
}
|
|
}
|
|
fn update_text(
|
|
controls: Option<ResMut<WeightsControl>>,
|
|
text: Single<Entity, With<Text>>,
|
|
morphs: Query<&MorphWeights>,
|
|
mut writer: TextUiWriter,
|
|
) {
|
|
let Some(mut controls) = controls else {
|
|
return;
|
|
};
|
|
for (i, target) in controls.weights.iter_mut().enumerate() {
|
|
let Ok(weights) = morphs.get(target.entity) else {
|
|
continue;
|
|
};
|
|
let Some(&actual_weight) = weights.weights().get(target.index) else {
|
|
continue;
|
|
};
|
|
if actual_weight != target.weight {
|
|
target.weight = actual_weight;
|
|
}
|
|
let key_name = &AVAILABLE_KEYS[i].name;
|
|
*writer.text(*text, i + 3) = format!("[{key_name}] {target}\n");
|
|
}
|
|
}
|
|
fn update_morphs(
|
|
controls: Option<ResMut<WeightsControl>>,
|
|
mut morphs: Query<&mut MorphWeights>,
|
|
input: Res<ButtonInput<KeyCode>>,
|
|
time: Res<Time>,
|
|
) {
|
|
let Some(mut controls) = controls else {
|
|
return;
|
|
};
|
|
for (i, target) in controls.weights.iter_mut().enumerate() {
|
|
if !AVAILABLE_KEYS[i].active(&input) {
|
|
continue;
|
|
}
|
|
let Ok(mut weights) = morphs.get_mut(target.entity) else {
|
|
continue;
|
|
};
|
|
// To update individual morph target weights, get the `MorphWeights`
|
|
// component and call `weights_mut` to get access to the weights.
|
|
let weights_slice = weights.weights_mut();
|
|
let i = target.index;
|
|
let change = time.delta_secs() * WEIGHT_PER_SECOND;
|
|
let new_weight = target.change_dir.change_weight(weights_slice[i], change);
|
|
weights_slice[i] = new_weight;
|
|
target.weight = new_weight;
|
|
}
|
|
}
|
|
|
|
fn detect_morphs(
|
|
mut commands: Commands,
|
|
morphs: Query<(Entity, &MorphWeights, Option<&Name>)>,
|
|
meshes: Res<Assets<Mesh>>,
|
|
scene_handle: Res<SceneHandle>,
|
|
mut setup: Local<bool>,
|
|
) {
|
|
let no_morphing = morphs.iter().len() == 0;
|
|
if no_morphing {
|
|
return;
|
|
}
|
|
if scene_handle.is_loaded && !*setup {
|
|
*setup = true;
|
|
} else {
|
|
return;
|
|
}
|
|
let mut detected = Vec::new();
|
|
|
|
for (entity, weights, name) in &morphs {
|
|
let target_names = weights
|
|
.first_mesh()
|
|
.and_then(|h| meshes.get(h))
|
|
.and_then(|m| m.morph_target_names());
|
|
let targets = Target::new(name, weights.weights(), target_names, entity);
|
|
detected.extend(targets);
|
|
}
|
|
detected.truncate(AVAILABLE_KEYS.len());
|
|
let style = TextFont {
|
|
font_size: 13.0,
|
|
..default()
|
|
};
|
|
let mut spans = vec![
|
|
("Morph Target Controls\n".into(), style.clone()),
|
|
("---------------\n".into(), style.clone()),
|
|
];
|
|
let target_to_text =
|
|
|(i, target): (usize, &Target)| target.text_span(AVAILABLE_KEYS[i].name, style.clone());
|
|
spans.extend(detected.iter().enumerate().map(target_to_text));
|
|
commands.insert_resource(WeightsControl { weights: detected });
|
|
commands
|
|
.spawn((
|
|
Text::default(),
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(10.0),
|
|
left: Val::Px(10.0),
|
|
..default()
|
|
},
|
|
))
|
|
.with_children(|p| {
|
|
p.spawn((TextSpan::new("Morph Target Controls\n"), style.clone()));
|
|
p.spawn((TextSpan::new("---------------\n"), style));
|
|
});
|
|
}
|
|
|
|
pub struct MorphViewerPlugin;
|
|
|
|
impl Plugin for MorphViewerPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(
|
|
Update,
|
|
(
|
|
update_morphs,
|
|
detect_morphs,
|
|
update_text.after(update_morphs),
|
|
),
|
|
);
|
|
}
|
|
}
|