Add a bounding box gizmo (#8468)
# Objective Add a bounding box gizmo  ## Changes - Added the `AabbGizmo` component that will draw the `Aabb` component on that entity. - Added an option to draw all bounding boxes in a scene on the `GizmoConfig` resource. - Added `TransformPoint` trait to generalize over the point transformation methods on various transform types (e.g `Transform` and `GlobalTransform`). - Changed the `Gizmos::cuboid` method to accept an `impl TransformPoint` instead of separate translation, rotation, and scale.
This commit is contained in:
parent
7f78e063af
commit
b5d24d8fb2
@ -21,3 +21,4 @@ bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.11.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.11.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.11.0-dev" }
|
||||
|
@ -7,7 +7,8 @@ use bevy_ecs::{
|
||||
world::World,
|
||||
};
|
||||
use bevy_math::{Mat2, Quat, Vec2, Vec3};
|
||||
use bevy_render::prelude::Color;
|
||||
use bevy_render::color::Color;
|
||||
use bevy_transform::TransformPoint;
|
||||
|
||||
type PositionItem = [f32; 3];
|
||||
type ColorItem = [f32; 4];
|
||||
@ -280,27 +281,31 @@ impl<'s> Gizmos<'s> {
|
||||
/// ```
|
||||
/// # use bevy_gizmos::prelude::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
/// # use bevy_math::prelude::*;
|
||||
/// # use bevy_transform::prelude::*;
|
||||
/// fn system(mut gizmos: Gizmos) {
|
||||
/// gizmos.cuboid(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE, Color::GREEN);
|
||||
/// gizmos.cuboid(Transform::IDENTITY, Color::GREEN);
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) {
|
||||
let rect = rect_inner(size.truncate());
|
||||
pub fn cuboid(&mut self, transform: impl TransformPoint, color: Color) {
|
||||
let rect = rect_inner(Vec2::ONE);
|
||||
// Front
|
||||
let [tlf, trf, brf, blf] = rect.map(|vec2| position + rotation * vec2.extend(size.z / 2.));
|
||||
let [tlf, trf, brf, blf] = rect.map(|vec2| transform.transform_point(vec2.extend(0.5)));
|
||||
// Back
|
||||
let [tlb, trb, brb, blb] = rect.map(|vec2| position + rotation * vec2.extend(-size.z / 2.));
|
||||
let [tlb, trb, brb, blb] = rect.map(|vec2| transform.transform_point(vec2.extend(-0.5)));
|
||||
|
||||
let positions = [
|
||||
tlf, trf, trf, brf, brf, blf, blf, tlf, // Front
|
||||
tlb, trb, trb, brb, brb, blb, blb, tlb, // Back
|
||||
tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back
|
||||
let strip_positions = [
|
||||
tlf, trf, brf, blf, tlf, // Front
|
||||
tlb, trb, brb, blb, tlb, // Back
|
||||
];
|
||||
self.extend_list_positions(positions);
|
||||
self.add_list_color(color, 24);
|
||||
self.linestrip(strip_positions, color);
|
||||
|
||||
let list_positions = [
|
||||
trf, trb, brf, brb, blf, blb, // Front to back
|
||||
];
|
||||
self.extend_list_positions(list_positions);
|
||||
self.add_list_color(color, 6);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
|
@ -18,22 +18,31 @@
|
||||
|
||||
use std::mem;
|
||||
|
||||
use bevy_app::{Last, Plugin};
|
||||
use bevy_app::{Last, Plugin, Update};
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, DetectChanges},
|
||||
change_detection::DetectChanges,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::Without,
|
||||
reflect::ReflectComponent,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Res, ResMut, Resource},
|
||||
system::{Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_reflect::{
|
||||
std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypeUuid,
|
||||
};
|
||||
use bevy_render::{
|
||||
color::Color,
|
||||
mesh::Mesh,
|
||||
primitives::Aabb,
|
||||
render_phase::AddRenderCommand,
|
||||
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
use bevy_pbr::MeshUniform;
|
||||
@ -47,12 +56,12 @@ mod pipeline_2d;
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
mod pipeline_3d;
|
||||
|
||||
use crate::gizmos::GizmoStorage;
|
||||
use gizmos::{GizmoStorage, Gizmos};
|
||||
|
||||
/// The `bevy_gizmos` prelude.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{gizmos::Gizmos, GizmoConfig};
|
||||
pub use crate::{gizmos::Gizmos, AabbGizmo, AabbGizmoConfig, GizmoConfig};
|
||||
}
|
||||
|
||||
const LINE_SHADER_HANDLE: HandleUntyped =
|
||||
@ -68,7 +77,14 @@ impl Plugin for GizmoPlugin {
|
||||
app.init_resource::<MeshHandles>()
|
||||
.init_resource::<GizmoConfig>()
|
||||
.init_resource::<GizmoStorage>()
|
||||
.add_systems(Last, update_gizmo_meshes);
|
||||
.add_systems(Last, update_gizmo_meshes)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
draw_aabbs,
|
||||
draw_all_aabbs.run_if(|config: Res<GizmoConfig>| config.aabb.draw_all),
|
||||
),
|
||||
);
|
||||
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
@ -101,7 +117,7 @@ impl Plugin for GizmoPlugin {
|
||||
}
|
||||
|
||||
/// A [`Resource`] that stores configuration for gizmos.
|
||||
#[derive(Resource, Clone, Copy)]
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct GizmoConfig {
|
||||
/// Set to `false` to stop drawing gizmos.
|
||||
///
|
||||
@ -113,6 +129,8 @@ pub struct GizmoConfig {
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub on_top: bool,
|
||||
/// Configuration for the [`AabbGizmo`].
|
||||
pub aabb: AabbGizmoConfig,
|
||||
}
|
||||
|
||||
impl Default for GizmoConfig {
|
||||
@ -120,10 +138,79 @@ impl Default for GizmoConfig {
|
||||
Self {
|
||||
enabled: true,
|
||||
on_top: false,
|
||||
aabb: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for drawing the [`Aabb`] component on entities.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AabbGizmoConfig {
|
||||
/// Draws all bounding boxes in the scene when set to `true`.
|
||||
///
|
||||
/// To draw a specific entity's bounding box, you can add the [`AabbGizmo`] component.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub draw_all: bool,
|
||||
/// The default color for bounding box gizmos.
|
||||
///
|
||||
/// A random color is chosen per box if `None`.
|
||||
///
|
||||
/// Defaults to `None`.
|
||||
pub default_color: Option<Color>,
|
||||
}
|
||||
|
||||
/// Add this [`Component`] to an entity to draw its [`Aabb`] component.
|
||||
#[derive(Component, Reflect, FromReflect, Default, Debug)]
|
||||
#[reflect(Component, FromReflect, Default)]
|
||||
pub struct AabbGizmo {
|
||||
/// The color of the box.
|
||||
///
|
||||
/// The default color from the [`GizmoConfig`] resource is used if `None`,
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
fn draw_aabbs(
|
||||
query: Query<(Entity, &Aabb, &GlobalTransform, &AabbGizmo)>,
|
||||
config: Res<GizmoConfig>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (entity, &aabb, &transform, gizmo) in &query {
|
||||
let color = gizmo
|
||||
.color
|
||||
.or(config.aabb.default_color)
|
||||
.unwrap_or_else(|| color_from_entity(entity));
|
||||
gizmos.cuboid(aabb_transform(aabb, transform), color);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_all_aabbs(
|
||||
query: Query<(Entity, &Aabb, &GlobalTransform), Without<AabbGizmo>>,
|
||||
config: Res<GizmoConfig>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
for (entity, &aabb, &transform) in &query {
|
||||
let color = config
|
||||
.aabb
|
||||
.default_color
|
||||
.unwrap_or_else(|| color_from_entity(entity));
|
||||
gizmos.cuboid(aabb_transform(aabb, transform), color);
|
||||
}
|
||||
}
|
||||
|
||||
fn color_from_entity(entity: Entity) -> Color {
|
||||
let hue = entity.to_bits() as f32 * 100_000. % 360.;
|
||||
Color::hsl(hue, 1., 0.5)
|
||||
}
|
||||
|
||||
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
|
||||
transform
|
||||
* GlobalTransform::from(
|
||||
Transform::from_translation(aabb.center.into())
|
||||
.with_scale((aabb.half_extents * 2.).into()),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MeshHandles {
|
||||
list: Option<Handle<Mesh>>,
|
||||
@ -198,7 +285,7 @@ fn extract_gizmo_data(
|
||||
config: Extract<Res<GizmoConfig>>,
|
||||
) {
|
||||
if config.is_changed() {
|
||||
commands.insert_resource(**config);
|
||||
commands.insert_resource(config.clone());
|
||||
}
|
||||
|
||||
if !config.enabled {
|
||||
|
@ -14,12 +14,15 @@ pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
|
||||
TransformPoint,
|
||||
};
|
||||
}
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::ValidParentCheckPlugin;
|
||||
use bevy_math::{Affine3A, Mat4, Vec3};
|
||||
|
||||
use prelude::{GlobalTransform, Transform};
|
||||
use systems::{propagate_transforms, sync_simple_transforms};
|
||||
|
||||
@ -131,3 +134,37 @@ impl Plugin for TransformPlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for point transformation methods.
|
||||
pub trait TransformPoint {
|
||||
/// Transform a point.
|
||||
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3;
|
||||
}
|
||||
|
||||
impl TransformPoint for Transform {
|
||||
#[inline]
|
||||
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
|
||||
self.transform_point(point.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformPoint for GlobalTransform {
|
||||
#[inline]
|
||||
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
|
||||
self.transform_point(point.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformPoint for Mat4 {
|
||||
#[inline]
|
||||
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
|
||||
self.transform_point3(point.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformPoint for Affine3A {
|
||||
#[inline]
|
||||
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
|
||||
self.transform_point3(point.into())
|
||||
}
|
||||
}
|
||||
|
@ -56,9 +56,7 @@ fn setup(
|
||||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
gizmos.cuboid(
|
||||
Vec3::Y * -0.5,
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(5., 1., 2.),
|
||||
Transform::from_translation(Vec3::Y * -0.5).with_scale(Vec3::new(5., 1., 2.)),
|
||||
Color::BLACK,
|
||||
);
|
||||
gizmos.rect(
|
||||
|
@ -3,7 +3,10 @@
|
||||
//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.
|
||||
//! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`.
|
||||
|
||||
use bevy::{asset::LoadState, gltf::Gltf, prelude::*, scene::InstanceId};
|
||||
use bevy::{
|
||||
asset::LoadState, gltf::Gltf, input::common_conditions::input_just_pressed, prelude::*,
|
||||
scene::InstanceId,
|
||||
};
|
||||
|
||||
use std::f32::consts::*;
|
||||
use std::fmt;
|
||||
@ -43,6 +46,7 @@ impl fmt::Display for SceneHandle {
|
||||
Scene Controls:
|
||||
L - animate light direction
|
||||
U - toggle shadows
|
||||
B - toggle bounding boxes
|
||||
C - cycle through the camera controller and any cameras loaded from the scene
|
||||
|
||||
Space - Play/Pause animation
|
||||
@ -63,6 +67,7 @@ impl Plugin for SceneViewerPlugin {
|
||||
(
|
||||
update_lights,
|
||||
camera_tracker,
|
||||
toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::B)),
|
||||
#[cfg(feature = "animation")]
|
||||
(start_animation, keyboard_animation_control),
|
||||
),
|
||||
@ -70,6 +75,10 @@ impl Plugin for SceneViewerPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_bounding_boxes(mut config: ResMut<GizmoConfig>) {
|
||||
config.aabb.draw_all ^= true;
|
||||
}
|
||||
|
||||
fn scene_load_check(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut scenes: ResMut<Assets<Scene>>,
|
||||
|
Loading…
Reference in New Issue
Block a user