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_core = { path = "../bevy_core", version = "0.11.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", 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_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,
|
world::World,
|
||||||
};
|
};
|
||||||
use bevy_math::{Mat2, Quat, Vec2, Vec3};
|
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 PositionItem = [f32; 3];
|
||||||
type ColorItem = [f32; 4];
|
type ColorItem = [f32; 4];
|
||||||
@ -280,27 +281,31 @@ impl<'s> Gizmos<'s> {
|
|||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_gizmos::prelude::*;
|
/// # use bevy_gizmos::prelude::*;
|
||||||
/// # use bevy_render::prelude::*;
|
/// # use bevy_render::prelude::*;
|
||||||
/// # use bevy_math::prelude::*;
|
/// # use bevy_transform::prelude::*;
|
||||||
/// fn system(mut gizmos: Gizmos) {
|
/// 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);
|
/// # bevy_ecs::system::assert_is_system(system);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) {
|
pub fn cuboid(&mut self, transform: impl TransformPoint, color: Color) {
|
||||||
let rect = rect_inner(size.truncate());
|
let rect = rect_inner(Vec2::ONE);
|
||||||
// Front
|
// 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
|
// 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 = [
|
let strip_positions = [
|
||||||
tlf, trf, trf, brf, brf, blf, blf, tlf, // Front
|
tlf, trf, brf, blf, tlf, // Front
|
||||||
tlb, trb, trb, brb, brb, blb, blb, tlb, // Back
|
tlb, trb, brb, blb, tlb, // Back
|
||||||
tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back
|
|
||||||
];
|
];
|
||||||
self.extend_list_positions(positions);
|
self.linestrip(strip_positions, color);
|
||||||
self.add_list_color(color, 24);
|
|
||||||
|
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`.
|
/// Draw a line from `start` to `end`.
|
||||||
|
@ -18,22 +18,31 @@
|
|||||||
|
|
||||||
use std::mem;
|
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_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::{Component, DetectChanges},
|
change_detection::DetectChanges,
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
query::Without,
|
||||||
|
reflect::ReflectComponent,
|
||||||
schedule::IntoSystemConfigs,
|
schedule::IntoSystemConfigs,
|
||||||
system::{Commands, Res, ResMut, Resource},
|
system::{Commands, Query, Res, ResMut, Resource},
|
||||||
world::{FromWorld, World},
|
world::{FromWorld, World},
|
||||||
};
|
};
|
||||||
use bevy_math::Mat4;
|
use bevy_math::Mat4;
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::{
|
||||||
|
std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect, TypeUuid,
|
||||||
|
};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
|
color::Color,
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
|
primitives::Aabb,
|
||||||
render_phase::AddRenderCommand,
|
render_phase::AddRenderCommand,
|
||||||
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
|
use bevy_transform::components::{GlobalTransform, Transform};
|
||||||
|
|
||||||
#[cfg(feature = "bevy_pbr")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
use bevy_pbr::MeshUniform;
|
use bevy_pbr::MeshUniform;
|
||||||
@ -47,12 +56,12 @@ mod pipeline_2d;
|
|||||||
#[cfg(feature = "bevy_pbr")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
mod pipeline_3d;
|
mod pipeline_3d;
|
||||||
|
|
||||||
use crate::gizmos::GizmoStorage;
|
use gizmos::{GizmoStorage, Gizmos};
|
||||||
|
|
||||||
/// The `bevy_gizmos` prelude.
|
/// The `bevy_gizmos` prelude.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{gizmos::Gizmos, GizmoConfig};
|
pub use crate::{gizmos::Gizmos, AabbGizmo, AabbGizmoConfig, GizmoConfig};
|
||||||
}
|
}
|
||||||
|
|
||||||
const LINE_SHADER_HANDLE: HandleUntyped =
|
const LINE_SHADER_HANDLE: HandleUntyped =
|
||||||
@ -68,7 +77,14 @@ impl Plugin for GizmoPlugin {
|
|||||||
app.init_resource::<MeshHandles>()
|
app.init_resource::<MeshHandles>()
|
||||||
.init_resource::<GizmoConfig>()
|
.init_resource::<GizmoConfig>()
|
||||||
.init_resource::<GizmoStorage>()
|
.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; };
|
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.
|
/// A [`Resource`] that stores configuration for gizmos.
|
||||||
#[derive(Resource, Clone, Copy)]
|
#[derive(Resource, Clone)]
|
||||||
pub struct GizmoConfig {
|
pub struct GizmoConfig {
|
||||||
/// Set to `false` to stop drawing gizmos.
|
/// Set to `false` to stop drawing gizmos.
|
||||||
///
|
///
|
||||||
@ -113,6 +129,8 @@ pub struct GizmoConfig {
|
|||||||
///
|
///
|
||||||
/// Defaults to `false`.
|
/// Defaults to `false`.
|
||||||
pub on_top: bool,
|
pub on_top: bool,
|
||||||
|
/// Configuration for the [`AabbGizmo`].
|
||||||
|
pub aabb: AabbGizmoConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GizmoConfig {
|
impl Default for GizmoConfig {
|
||||||
@ -120,10 +138,79 @@ impl Default for GizmoConfig {
|
|||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
on_top: false,
|
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)]
|
#[derive(Resource)]
|
||||||
struct MeshHandles {
|
struct MeshHandles {
|
||||||
list: Option<Handle<Mesh>>,
|
list: Option<Handle<Mesh>>,
|
||||||
@ -198,7 +285,7 @@ fn extract_gizmo_data(
|
|||||||
config: Extract<Res<GizmoConfig>>,
|
config: Extract<Res<GizmoConfig>>,
|
||||||
) {
|
) {
|
||||||
if config.is_changed() {
|
if config.is_changed() {
|
||||||
commands.insert_resource(**config);
|
commands.insert_resource(config.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.enabled {
|
if !config.enabled {
|
||||||
|
@ -14,12 +14,15 @@ pub mod prelude {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
|
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
|
||||||
|
TransformPoint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_hierarchy::ValidParentCheckPlugin;
|
use bevy_hierarchy::ValidParentCheckPlugin;
|
||||||
|
use bevy_math::{Affine3A, Mat4, Vec3};
|
||||||
|
|
||||||
use prelude::{GlobalTransform, Transform};
|
use prelude::{GlobalTransform, Transform};
|
||||||
use systems::{propagate_transforms, sync_simple_transforms};
|
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>) {
|
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||||
gizmos.cuboid(
|
gizmos.cuboid(
|
||||||
Vec3::Y * -0.5,
|
Transform::from_translation(Vec3::Y * -0.5).with_scale(Vec3::new(5., 1., 2.)),
|
||||||
Quat::IDENTITY,
|
|
||||||
Vec3::new(5., 1., 2.),
|
|
||||||
Color::BLACK,
|
Color::BLACK,
|
||||||
);
|
);
|
||||||
gizmos.rect(
|
gizmos.rect(
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.
|
//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.
|
||||||
//! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`.
|
//! - 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::f32::consts::*;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@ -43,6 +46,7 @@ impl fmt::Display for SceneHandle {
|
|||||||
Scene Controls:
|
Scene Controls:
|
||||||
L - animate light direction
|
L - animate light direction
|
||||||
U - toggle shadows
|
U - toggle shadows
|
||||||
|
B - toggle bounding boxes
|
||||||
C - cycle through the camera controller and any cameras loaded from the scene
|
C - cycle through the camera controller and any cameras loaded from the scene
|
||||||
|
|
||||||
Space - Play/Pause animation
|
Space - Play/Pause animation
|
||||||
@ -63,6 +67,7 @@ impl Plugin for SceneViewerPlugin {
|
|||||||
(
|
(
|
||||||
update_lights,
|
update_lights,
|
||||||
camera_tracker,
|
camera_tracker,
|
||||||
|
toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::B)),
|
||||||
#[cfg(feature = "animation")]
|
#[cfg(feature = "animation")]
|
||||||
(start_animation, keyboard_animation_control),
|
(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(
|
fn scene_load_check(
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
mut scenes: ResMut<Assets<Scene>>,
|
mut scenes: ResMut<Assets<Scene>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user