Skip to content

Commit 9e0a263

Browse files
authored
feat: add labels to created colliders (#100)
1 parent 7d26158 commit 9e0a263

8 files changed

Lines changed: 171 additions & 4 deletions

File tree

assets/skeleton_colliders/human.coll.ron

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
translation: (0.0, 0.25, -0.025),
2222
),
2323
offset_mode: Global,
24+
label: "shin",
25+
use_suffixes: true,
2426
),
2527
],
2628
skeleton: "skeletons/human.skn.ron",
@@ -34,4 +36,8 @@
3436
mode: MirrorX,
3537
),
3638
symmetry_enabled: true,
39+
default_layer_membership: 1,
40+
default_layer_filter: 0,
41+
suffix: ".R",
42+
mirror_suffix: ".L",
3743
)

crates/bevy_animation_graph/src/core/animated_scene/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,9 @@ fn process_scene_into_animscn(
214214
.into(),
215215
},
216216
offset_mode: cfg.offset_mode,
217+
label: cfg.label.clone(),
218+
use_suffixes: cfg.use_suffixes,
219+
is_mirrored: true,
217220
};
218221
let mirror_bone_collider_list = foreach_bone.entry(mirror_bone_id).or_default();
219222
mirror_bone_collider_list.push(mirror_cfg);
@@ -233,6 +236,8 @@ fn process_scene_into_animscn(
233236
use avian3d::prelude::{ColliderConstructor, CollisionLayers};
234237

235238
let cfg_to_bundle = |cfg: ColliderConfig| {
239+
use crate::core::colliders::core::ColliderLabel;
240+
236241
(
237242
cfg.local_transform(default_transforms),
238243
match cfg.shape {
@@ -262,6 +267,19 @@ fn process_scene_into_animscn(
262267
skeleton_colliders.default_layer_filter,
263268
)
264269
},
270+
ColliderLabel(if cfg.use_suffixes {
271+
format!(
272+
"{}{}",
273+
cfg.label.clone(),
274+
if cfg.is_mirrored {
275+
&skeleton_colliders.mirror_suffix
276+
} else {
277+
&skeleton_colliders.suffix
278+
}
279+
)
280+
} else {
281+
cfg.label.clone()
282+
}),
265283
)
266284
};
267285

crates/bevy_animation_graph/src/core/colliders/core.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bevy::{
22
asset::{Asset, Handle},
3+
ecs::{component::Component, reflect::ReflectComponent},
34
math::{
45
Isometry3d, Vec3,
56
primitives::{Capsule3d, Cuboid, Sphere},
@@ -46,6 +47,10 @@ pub enum ColliderOffsetMode {
4647
Global,
4748
}
4849

50+
#[derive(Component, Default, Clone, Reflect, PartialEq, Eq)]
51+
#[reflect(Component)]
52+
pub struct ColliderLabel(pub String);
53+
4954
#[derive(Debug, Clone, Reflect)]
5055
pub struct ColliderConfig {
5156
pub id: SkeletonColliderId,
@@ -56,6 +61,14 @@ pub struct ColliderConfig {
5661
pub attached_to: BoneId,
5762
pub offset: Isometry3d,
5863
pub offset_mode: ColliderOffsetMode,
64+
/// Label that will be attached to the created collider in a [`ColliderLabel`] component.
65+
pub label: String,
66+
/// Whether to apply suffixes to this collider and its mirror
67+
pub use_suffixes: bool,
68+
/// Whether this collider resulting from mirroring another collider.
69+
///
70+
/// Should always be false on manually created colliders
71+
pub is_mirrored: bool,
5972
}
6073

6174
impl ColliderConfig {
@@ -83,6 +96,9 @@ impl Default for ColliderConfig {
8396
attached_to: BoneId::default(),
8497
offset: Isometry3d::default(),
8598
offset_mode: ColliderOffsetMode::default(),
99+
label: "".to_string(),
100+
use_suffixes: false,
101+
is_mirrored: false,
86102
}
87103
}
88104
}
@@ -100,6 +116,11 @@ pub struct SkeletonColliders {
100116
pub default_layer_membership: u32,
101117
/// Default physics layer filters if not overriden
102118
pub default_layer_filter: u32,
119+
120+
/// Suffix optionally applied to collider labels
121+
pub suffix: String,
122+
/// Suffix optionally applied to collider labels corresponding to mirrored colliders
123+
pub mirror_suffix: String,
103124
}
104125

105126
impl SkeletonColliders {

crates/bevy_animation_graph/src/core/colliders/serial.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ pub struct ColliderConfigSerial {
2323
pub offset: Isometry3d,
2424
#[serde(default)]
2525
pub offset_mode: ColliderOffsetMode,
26+
#[serde(default)]
27+
pub label: String,
28+
#[serde(default)]
29+
pub use_suffixes: bool,
2630
}
2731

2832
#[derive(Debug, Clone, Default, Reflect, Serialize, Deserialize)]
@@ -34,6 +38,14 @@ pub struct SkeletonCollidersSerial {
3438
pub symmetry: SymmetryConfigSerial,
3539
#[serde(default)]
3640
pub symmetry_enabled: bool,
41+
#[serde(default)]
42+
default_layer_membership: u32,
43+
#[serde(default)]
44+
default_layer_filter: u32,
45+
#[serde(default)]
46+
suffix: String,
47+
#[serde(default)]
48+
mirror_suffix: String,
3749
}
3850

3951
impl SkeletonCollidersSerial {
@@ -50,6 +62,8 @@ impl SkeletonCollidersSerial {
5062
offset: config.offset,
5163
id: config.id,
5264
offset_mode: config.offset_mode,
65+
label: config.label.clone(),
66+
use_suffixes: config.use_suffixes,
5367
};
5468

5569
colliders.push(config_serial);
@@ -60,6 +74,10 @@ impl SkeletonCollidersSerial {
6074
skeleton: value.skeleton.path()?.to_owned(),
6175
symmetry: SymmetryConfigSerial::from_value(&value.symmetry),
6276
symmetry_enabled: value.symmetry_enabled,
77+
default_layer_membership: value.default_layer_membership,
78+
default_layer_filter: value.default_layer_filter,
79+
suffix: value.suffix.clone(),
80+
mirror_suffix: value.mirror_suffix.clone(),
6381
})
6482
}
6583

@@ -78,6 +96,10 @@ impl SkeletonCollidersSerial {
7896
colliders.skeleton = skeleton_handle;
7997
colliders.symmetry = self.symmetry.to_value().ok()?;
8098
colliders.symmetry_enabled = self.symmetry_enabled;
99+
colliders.default_layer_membership = self.default_layer_membership;
100+
colliders.default_layer_filter = self.default_layer_filter;
101+
colliders.suffix = self.suffix.clone();
102+
colliders.mirror_suffix = self.mirror_suffix.clone();
81103

82104
for config_serial in &self.colliders {
83105
let config = ColliderConfig {
@@ -89,6 +111,9 @@ impl SkeletonCollidersSerial {
89111
offset: config_serial.offset,
90112
id: config_serial.id,
91113
offset_mode: config_serial.offset_mode,
114+
label: config_serial.label.clone(),
115+
use_suffixes: config_serial.use_suffixes,
116+
is_mirrored: false,
92117
};
93118

94119
colliders.add_collider(config);

crates/bevy_animation_graph/src/core/plugin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use super::{
1515
state_machine::high_level::{StateMachine, loader::StateMachineLoader},
1616
systems::{animation_player, animation_player_deferred_gizmos},
1717
};
18+
use crate::core::colliders::core::ColliderLabel;
1819
use crate::nodes::blend_space_node::BlendSpaceNode;
1920
use crate::nodes::{
2021
AbsF32, AddF32, BlendMode, BlendNode, BlendSyncMode, ChainNode, ClampF32, ClipNode, CompareF32,
@@ -129,6 +130,7 @@ impl AnimationGraphPlugin {
129130
.register_type::<BlendMode>()
130131
.register_type::<BlendSyncMode>()
131132
.register_type::<GlobalTransition>()
133+
.register_type::<ColliderLabel>()
132134
.register_type::<()>()
133135
.register_type_data::<(), ReflectDefault>();
134136
}

crates/bevy_animation_graph_editor/src/ui/actions/colliders.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,31 @@ impl UpdateDefaultLayers {
156156
skeleton_colliders.default_layer_filter = input.layer_filter;
157157
}
158158
}
159+
160+
pub struct UpdateSuffixes {
161+
pub colliders: Handle<SkeletonColliders>,
162+
pub suffix: String,
163+
pub mirror_suffix: String,
164+
}
165+
166+
impl DynamicAction for UpdateSuffixes {
167+
fn handle(self: Box<Self>, world: &mut World) {
168+
run_handler(world, "Could not update suffixes config")(Self::system, *self)
169+
}
170+
}
171+
172+
impl UpdateSuffixes {
173+
pub fn system(
174+
In(input): In<Self>,
175+
mut skeleton_collider_assets: ResMut<Assets<SkeletonColliders>>,
176+
mut dirty_assets: ResMut<DirtyAssets>,
177+
) {
178+
let Some(skeleton_colliders) = skeleton_collider_assets.get_mut(&input.colliders) else {
179+
return;
180+
};
181+
182+
dirty_assets.add(input.colliders);
183+
skeleton_colliders.suffix = input.suffix;
184+
skeleton_colliders.mirror_suffix = input.mirror_suffix;
185+
}
186+
}

crates/bevy_animation_graph_editor/src/ui/editor_windows/skeleton_preview.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ use crate::{
4141
PartOfSubScene, PreviewScene, SubSceneConfig, SubSceneSyncAction,
4242
actions::{
4343
colliders::{
44-
CreateOrEditCollider, DeleteCollider, UpdateDefaultLayers, UpdateSymmetryConfig,
45-
UpdateSymmetryEnabled,
44+
CreateOrEditCollider, DeleteCollider, UpdateDefaultLayers, UpdateSuffixes,
45+
UpdateSymmetryConfig, UpdateSymmetryEnabled,
4646
},
4747
window::DynWindowAction,
4848
},
@@ -250,6 +250,31 @@ impl SkeletonCollidersPreviewWindow {
250250
layer_filter,
251251
});
252252
}
253+
254+
let mut suffix = skeleton_colliders.suffix.clone();
255+
let mut mirror_suffix = skeleton_colliders.mirror_suffix.clone();
256+
let mut changed = false;
257+
258+
ui.horizontal(|ui| {
259+
ui.label("Suffix");
260+
if ui.text_edit_singleline(&mut suffix).changed() {
261+
changed = true;
262+
}
263+
});
264+
ui.horizontal(|ui| {
265+
ui.label("Mirror suffix");
266+
if ui.text_edit_singleline(&mut mirror_suffix).changed() {
267+
changed = true
268+
}
269+
});
270+
271+
if changed {
272+
ctx.editor_actions.dynamic(UpdateSuffixes {
273+
colliders: target.clone(),
274+
suffix,
275+
mirror_suffix,
276+
});
277+
}
253278
});
254279

255280
ui.separator();
@@ -488,6 +513,11 @@ impl SkeletonCollidersPreviewWindow {
488513
config.shape = new_shape;
489514
}
490515

516+
ui.horizontal(|ui| {
517+
ui.label("Label");
518+
ui.text_edit_singleline(&mut config.label);
519+
});
520+
ui.checkbox(&mut config.use_suffixes, "Use suffixes");
491521
ui.label("Physics layers");
492522
ui.checkbox(&mut config.override_layers, "Override global");
493523
ui.horizontal(|ui| {

examples/human_colliders/examples/human_colliders.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ extern crate bevy;
22
extern crate bevy_animation_graph;
33

44
use avian3d::PhysicsPlugins;
5-
use avian3d::prelude::PhysicsDebugPlugin;
5+
use avian3d::prelude::{PhysicsDebugPlugin, SpatialQuery, SpatialQueryFilter};
6+
use bevy::window::PrimaryWindow;
67
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
78
use bevy_animation_graph::core::animated_scene::AnimatedSceneInstance;
9+
use bevy_animation_graph::core::colliders::core::ColliderLabel;
810
use bevy_animation_graph::prelude::*;
911
use std::f32::consts::PI;
1012

@@ -24,7 +26,7 @@ fn main() {
2426
})
2527
.insert_resource(Params::default())
2628
.add_systems(Startup, setup)
27-
.add_systems(Update, keyboard_animation_control)
29+
.add_systems(Update, (keyboard_animation_control, raycast_ui))
2830
.run();
2931
}
3032

@@ -88,6 +90,9 @@ fn setup(
8890
Human,
8991
));
9092

93+
// Collider label text
94+
commands.spawn(Text::new(""));
95+
9196
println!("Controls:");
9297
println!("\tSPACE: Play/Pause animation");
9398
println!("\tR: Reset animation");
@@ -144,3 +149,35 @@ fn keyboard_animation_control(
144149
player.set_input_parameter("target_speed", params.speed.into());
145150
player.set_input_parameter("target_direction", params.direction.into());
146151
}
152+
153+
fn raycast_ui(
154+
spatial_query: SpatialQuery,
155+
main_camera: Single<(&Camera, &GlobalTransform)>,
156+
main_window: Single<&Window, With<PrimaryWindow>>,
157+
collider_label_query: Query<&ColliderLabel>,
158+
debug_text: Single<(&mut Text, &mut Node)>,
159+
) {
160+
let (camera, camera_transform) = main_camera.into_inner();
161+
let cursor = main_window.cursor_position().unwrap_or_default();
162+
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor) else {
163+
return;
164+
};
165+
166+
let (mut debug_text, mut debug_node) = debug_text.into_inner();
167+
168+
if let Some(hit) = spatial_query.cast_ray(
169+
ray.origin,
170+
ray.direction,
171+
100000.,
172+
true,
173+
&SpatialQueryFilter::DEFAULT,
174+
) && let Ok(label) = collider_label_query.get(hit.entity)
175+
{
176+
debug_text.0 = label.0.clone();
177+
debug_node.position_type = PositionType::Absolute;
178+
debug_node.top = Val::Px(cursor.y - 5.);
179+
debug_node.left = Val::Px(cursor.x + 5.);
180+
} else {
181+
debug_text.0 = "".into();
182+
}
183+
}

0 commit comments

Comments
 (0)