Skip to main content

bevy_gizmos/
skinned_mesh_bounds.rs

1//! A module adding debug visualization of [`DynamicSkinnedMeshBounds`].
2
3use bevy_app::{Plugin, PostUpdate};
4use bevy_asset::Assets;
5use bevy_camera::visibility::DynamicSkinnedMeshBounds;
6use bevy_color::Color;
7use bevy_ecs::{
8    component::Component,
9    query::{With, Without},
10    reflect::ReflectComponent,
11    schedule::IntoScheduleConfigs,
12    system::{Query, Res},
13};
14use bevy_math::Affine3A;
15use bevy_mesh::{
16    mark_3d_meshes_as_changed_if_their_assets_changed,
17    skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
18    Mesh, Mesh3d,
19};
20use bevy_reflect::{std_traits::ReflectDefault, Reflect};
21use bevy_transform::{components::GlobalTransform, TransformSystems};
22
23use crate::{
24    config::{GizmoConfigGroup, GizmoConfigStore},
25    gizmos::Gizmos,
26    AppGizmoBuilder,
27};
28
29/// A [`Plugin`] that provides visualization of entities with [`DynamicSkinnedMeshBounds`].
30pub struct SkinnedMeshBoundsGizmoPlugin;
31
32impl Plugin for SkinnedMeshBoundsGizmoPlugin {
33    fn build(&self, app: &mut bevy_app::App) {
34        app.init_gizmo_group::<SkinnedMeshBoundsGizmoConfigGroup>()
35            .add_systems(
36                PostUpdate,
37                (
38                    draw_skinned_mesh_bounds,
39                    draw_all_skinned_mesh_bounds.run_if(|config: Res<GizmoConfigStore>| {
40                        config
41                            .config::<SkinnedMeshBoundsGizmoConfigGroup>()
42                            .1
43                            .draw_all
44                    }),
45                )
46                    .after(TransformSystems::Propagate)
47                    .ambiguous_with(mark_3d_meshes_as_changed_if_their_assets_changed),
48            );
49    }
50}
51/// The [`GizmoConfigGroup`] used for debug visualizations of entities with [`DynamicSkinnedMeshBounds`]
52#[derive(Clone, Reflect, GizmoConfigGroup)]
53#[reflect(Clone, Default)]
54pub struct SkinnedMeshBoundsGizmoConfigGroup {
55    /// When set to `true`, draws all the bounds that contribute to skinned mesh
56    /// bounds.
57    ///
58    /// To draw a specific entity's skinned mesh bounds, you can add the [`ShowSkinnedMeshBoundsGizmo`] component.
59    ///
60    /// Defaults to `false`.
61    pub draw_all: bool,
62    /// The default color for skinned mesh bounds gizmos.
63    pub default_color: Color,
64}
65
66impl Default for SkinnedMeshBoundsGizmoConfigGroup {
67    fn default() -> Self {
68        Self {
69            draw_all: false,
70            default_color: Color::WHITE,
71        }
72    }
73}
74
75/// Add this [`Component`] to an entity to draw its [`DynamicSkinnedMeshBounds`] component.
76#[derive(Component, Reflect, Default, Debug)]
77#[reflect(Component, Default, Debug)]
78pub struct ShowSkinnedMeshBoundsGizmo {
79    /// The color of the bounds.
80    ///
81    /// The default color from the [`SkinnedMeshBoundsGizmoConfigGroup`] config is used if `None`,
82    pub color: Option<Color>,
83}
84
85fn draw(
86    color: Color,
87    mesh: &Mesh3d,
88    mesh_assets: &Res<Assets<Mesh>>,
89    skinned_mesh: &SkinnedMesh,
90    joint_entities: &Query<&GlobalTransform>,
91    inverse_bindposes_assets: &Res<Assets<SkinnedMeshInverseBindposes>>,
92    gizmos: &mut Gizmos<SkinnedMeshBoundsGizmoConfigGroup>,
93) {
94    if let Some(mesh_asset) = mesh_assets.get(mesh)
95        && let Some(bounds) = mesh_asset.skinned_mesh_bounds()
96        && let Some(inverse_bindposes_asset) =
97            inverse_bindposes_assets.get(&skinned_mesh.inverse_bindposes)
98    {
99        for (&joint_index, &joint_aabb) in bounds.iter() {
100            let joint_index = joint_index.0 as usize;
101
102            if let Some(&joint_entity) = skinned_mesh.joints.get(joint_index)
103                && let Ok(&world_from_joint) = joint_entities.get(joint_entity)
104                && let Some(&joint_from_mesh) = inverse_bindposes_asset.get(joint_index)
105            {
106                let world_from_mesh =
107                    world_from_joint.affine() * Affine3A::from_mat4(joint_from_mesh);
108
109                gizmos.aabb_3d(joint_aabb, world_from_mesh, color);
110            }
111        }
112    }
113}
114
115fn draw_skinned_mesh_bounds(
116    mesh_entities: Query<
117        (&Mesh3d, &SkinnedMesh, &ShowSkinnedMeshBoundsGizmo),
118        With<DynamicSkinnedMeshBounds>,
119    >,
120    joint_entities: Query<&GlobalTransform>,
121    mesh_assets: Res<Assets<Mesh>>,
122    inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>,
123    mut gizmos: Gizmos<SkinnedMeshBoundsGizmoConfigGroup>,
124) {
125    for (mesh, skinned_mesh, gizmo) in mesh_entities {
126        let color = gizmo.color.unwrap_or(gizmos.config_ext.default_color);
127
128        draw(
129            color,
130            mesh,
131            &mesh_assets,
132            skinned_mesh,
133            &joint_entities,
134            &inverse_bindposes_assets,
135            &mut gizmos,
136        );
137    }
138}
139
140fn draw_all_skinned_mesh_bounds(
141    mesh_entities: Query<
142        (&Mesh3d, &SkinnedMesh),
143        (
144            With<DynamicSkinnedMeshBounds>,
145            Without<ShowSkinnedMeshBoundsGizmo>,
146        ),
147    >,
148    joint_entities: Query<&GlobalTransform>,
149    mesh_assets: Res<Assets<Mesh>>,
150    inverse_bindposes_assets: Res<Assets<SkinnedMeshInverseBindposes>>,
151    mut gizmos: Gizmos<SkinnedMeshBoundsGizmoConfigGroup>,
152) {
153    for (mesh, skinned_mesh) in mesh_entities {
154        draw(
155            gizmos.config_ext.default_color,
156            mesh,
157            &mesh_assets,
158            skinned_mesh,
159            &joint_entities,
160            &inverse_bindposes_assets,
161            &mut gizmos,
162        );
163    }
164}