Add a bounding box gizmo (#8468)

# Objective

Add a bounding box gizmo

![Screenshot from 2023-04-22
23-49-40](https://user-images.githubusercontent.com/29694403/233808825-7593dc38-0623-48a9-b0d7-a4ca24a9e071.png)

## 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:
ira 2023-04-24 17:23:06 +02:00 committed by GitHub
parent 7f78e063af
commit b5d24d8fb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 26 deletions

View File

@ -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" }

View File

@ -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`.

View File

@ -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 {

View File

@ -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())
}
}

View File

@ -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(

View File

@ -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>>,