Skip to content

Commit 4c54edf

Browse files
committed
Add light gizmos
1 parent 499c978 commit 4c54edf

File tree

3 files changed

+216
-8
lines changed

3 files changed

+216
-8
lines changed

crates/bevy_gizmos/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod circles;
3232
pub mod config;
3333
pub mod gizmos;
3434
pub mod grid;
35+
pub mod light;
3536
pub mod primitives;
3637

3738
#[cfg(feature = "bevy_sprite")]
@@ -87,6 +88,8 @@ use config::{
8788
use gizmos::GizmoStorage;
8889
use std::{any::TypeId, mem};
8990

91+
use crate::light::LightGizmoPlugin;
92+
9093
const LINE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(7414812689238026784);
9194

9295
/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
@@ -108,7 +111,8 @@ impl Plugin for GizmoPlugin {
108111
.init_resource::<LineGizmoHandles>()
109112
// We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
110113
.init_gizmo_group::<DefaultGizmoConfigGroup>()
111-
.add_plugins(AabbGizmoPlugin);
114+
.add_plugins(AabbGizmoPlugin)
115+
.add_plugins(LightGizmoPlugin);
112116

113117
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
114118
return;

crates/bevy_gizmos/src/light.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
//! A module adding debug visualization of f [`PointLight`]s, [`SpotLight`]s and [`DirectionalLight`]s.
2+
3+
use std::f32::consts::PI;
4+
5+
use crate::{self as bevy_gizmos, primitives::dim3::GizmoPrimitive3d};
6+
7+
use bevy_app::{Plugin, PostUpdate};
8+
use bevy_color::LinearRgba;
9+
use bevy_ecs::{
10+
component::Component,
11+
query::{With, Without},
12+
reflect::ReflectComponent,
13+
schedule::IntoSystemConfigs,
14+
system::{Query, Res},
15+
};
16+
use bevy_math::{
17+
primitives::{Cone, Sphere},
18+
Quat, Vec3,
19+
};
20+
use bevy_pbr::{DirectionalLight, PointLight, SpotLight};
21+
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
22+
use bevy_transform::{components::GlobalTransform, TransformSystem};
23+
24+
use crate::{
25+
config::{GizmoConfigGroup, GizmoConfigStore},
26+
gizmos::Gizmos,
27+
AppGizmoBuilder,
28+
};
29+
30+
fn draw_gizmos<'a, P, S, D>(
31+
point_lights: P,
32+
spot_lights: S,
33+
directional_lights: D,
34+
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
35+
) where
36+
P: 'a + IntoIterator<Item = (&'a PointLight, &'a GlobalTransform)>,
37+
S: 'a + IntoIterator<Item = (&'a SpotLight, &'a GlobalTransform)>,
38+
D: 'a + IntoIterator<Item = (&'a DirectionalLight, &'a GlobalTransform)>,
39+
{
40+
// Standard sphere for the radius, axis sphere for the range.
41+
for (point_light, transform) in point_lights {
42+
let position = transform.translation();
43+
gizmos
44+
.primitive_3d(
45+
Sphere {
46+
radius: point_light.radius,
47+
},
48+
position,
49+
Quat::IDENTITY,
50+
point_light.color,
51+
)
52+
.segments(16);
53+
gizmos
54+
.sphere(
55+
position,
56+
Quat::IDENTITY,
57+
point_light.range,
58+
point_light.color,
59+
)
60+
.circle_segments(32);
61+
}
62+
63+
// A sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the
64+
// farthest point of effect of the spot light along its direction.
65+
for (spot_light, transform) in spot_lights {
66+
let (_, rotation, translation) = transform.to_scale_rotation_translation();
67+
gizmos
68+
.primitive_3d(
69+
Sphere {
70+
radius: spot_light.radius,
71+
},
72+
translation,
73+
Quat::IDENTITY,
74+
spot_light.color,
75+
)
76+
.segments(16);
77+
78+
// Offset the tip of the cone to the light position.
79+
gizmos.sphere(translation, Quat::IDENTITY, 0.01, LinearRgba::GREEN);
80+
for angle in [spot_light.inner_angle, spot_light.outer_angle] {
81+
let height = spot_light.range * angle.cos();
82+
let position = translation + rotation * Vec3::NEG_Z * height / 2.0;
83+
gizmos
84+
.primitive_3d(
85+
Cone {
86+
radius: spot_light.range * angle.sin(),
87+
height,
88+
},
89+
position,
90+
rotation * Quat::from_rotation_x(PI / 2.0),
91+
spot_light.color,
92+
)
93+
.height_segments(4)
94+
.base_segments(32);
95+
}
96+
97+
for arc_rotation in [
98+
Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle),
99+
Quat::from_euler(
100+
bevy_math::EulerRot::XZY,
101+
0.0,
102+
PI / 2.0,
103+
PI / 2.0 - spot_light.outer_angle,
104+
),
105+
] {
106+
gizmos
107+
.arc_3d(
108+
2.0 * spot_light.outer_angle,
109+
spot_light.range,
110+
translation,
111+
rotation * arc_rotation,
112+
spot_light.color,
113+
)
114+
.segments(16);
115+
}
116+
}
117+
118+
// An arrow alongside the directional light direction.
119+
for (directional_light, transform) in directional_lights {
120+
let (_, rotation, translation) = transform.to_scale_rotation_translation();
121+
gizmos
122+
.arrow(
123+
translation,
124+
translation + rotation * Vec3::NEG_Z,
125+
directional_light.color,
126+
)
127+
.with_tip_length(0.3);
128+
}
129+
}
130+
131+
/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s
132+
/// and [`DirectionalLight`]s for debugging.
133+
pub struct LightGizmoPlugin;
134+
135+
impl Plugin for LightGizmoPlugin {
136+
fn build(&self, app: &mut bevy_app::App) {
137+
app.register_type::<LightGizmoConfigGroup>()
138+
.init_gizmo_group::<LightGizmoConfigGroup>()
139+
.add_systems(
140+
PostUpdate,
141+
(
142+
draw_lights,
143+
draw_all_lights.run_if(|config: Res<GizmoConfigStore>| {
144+
config.config::<LightGizmoConfigGroup>().1.draw_all
145+
}),
146+
)
147+
.after(TransformSystem::TransformPropagate),
148+
);
149+
}
150+
}
151+
152+
/// The [`GizmoConfigGroup`] used to configure the visualization of lights.
153+
#[derive(Clone, Default, Reflect, GizmoConfigGroup)]
154+
pub struct LightGizmoConfigGroup {
155+
/// Draw a gizmo for all lights if true.
156+
pub draw_all: bool,
157+
}
158+
159+
/// Add this [`Component`] to an entity to draw any of its lights components
160+
/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]).
161+
#[derive(Component, Reflect, Default, Debug)]
162+
#[reflect(Component, Default)]
163+
pub struct ShowLightGizmo;
164+
165+
fn draw_lights(
166+
point_query: Query<(&PointLight, &GlobalTransform), With<ShowLightGizmo>>,
167+
spot_query: Query<(&SpotLight, &GlobalTransform), With<ShowLightGizmo>>,
168+
directional_query: Query<(&DirectionalLight, &GlobalTransform), With<ShowLightGizmo>>,
169+
mut gizmos: Gizmos<LightGizmoConfigGroup>,
170+
) {
171+
draw_gizmos(&point_query, &spot_query, &directional_query, &mut gizmos);
172+
}
173+
174+
fn draw_all_lights(
175+
point_query: Query<(&PointLight, &GlobalTransform), Without<ShowLightGizmo>>,
176+
spot_query: Query<(&SpotLight, &GlobalTransform), Without<ShowLightGizmo>>,
177+
directional_query: Query<(&DirectionalLight, &GlobalTransform), Without<ShowLightGizmo>>,
178+
mut gizmos: Gizmos<LightGizmoConfigGroup>,
179+
) {
180+
draw_gizmos(&point_query, &spot_query, &directional_query, &mut gizmos);
181+
}

crates/bevy_gizmos/src/primitives/dim3.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -610,13 +610,34 @@ pub struct Cone3dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
610610
color: Color,
611611

612612
// Number of segments used to approximate the cone geometry
613-
segments: usize,
613+
base_segments: usize,
614+
615+
height_segments: usize,
614616
}
615617

616618
impl<T: GizmoConfigGroup> Cone3dBuilder<'_, '_, '_, T> {
617-
/// Set the number of segments used to approximate the cone geometry.
619+
/// Set the number of segments used to approximate the cone geometry for its base and height.
618620
pub fn segments(mut self, segments: usize) -> Self {
619-
self.segments = segments;
621+
self.base_segments = segments;
622+
self.height_segments = segments;
623+
self
624+
}
625+
626+
/// Set the number of segments to approximate the height of the cone geometry.
627+
///
628+
/// `segments` should be a multiple of the value passed to [`Self::height_segments`]
629+
/// for the height to connect properly with the base.
630+
pub fn base_segments(mut self, segments: usize) -> Self {
631+
self.base_segments = segments;
632+
self
633+
}
634+
635+
/// Set the number of segments to approximate the height of the cone geometry.
636+
///
637+
/// `segments` should be a divider of the value passed to [`Self::base_segments`]
638+
/// for the height to connect properly with the base.
639+
pub fn height_segments(mut self, segments: usize) -> Self {
640+
self.height_segments = segments;
620641
self
621642
}
622643
}
@@ -638,7 +659,8 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d<Cone> for Gizmos<'w, 's, T> {
638659
position,
639660
rotation,
640661
color,
641-
segments: DEFAULT_NUMBER_SEGMENTS,
662+
base_segments: DEFAULT_NUMBER_SEGMENTS,
663+
height_segments: DEFAULT_NUMBER_SEGMENTS,
642664
}
643665
}
644666
}
@@ -656,7 +678,8 @@ impl<T: GizmoConfigGroup> Drop for Cone3dBuilder<'_, '_, '_, T> {
656678
position,
657679
rotation,
658680
color,
659-
segments,
681+
base_segments,
682+
height_segments,
660683
} = self;
661684

662685
let half_height = *height * 0.5;
@@ -665,15 +688,15 @@ impl<T: GizmoConfigGroup> Drop for Cone3dBuilder<'_, '_, '_, T> {
665688
draw_circle_3d(
666689
gizmos,
667690
*radius,
668-
*segments,
691+
*base_segments,
669692
*rotation,
670693
*position - *rotation * Vec3::Y * half_height,
671694
*color,
672695
);
673696

674697
// connect the base circle with the tip of the cone
675698
let end = Vec3::Y * half_height;
676-
circle_coordinates(*radius, *segments)
699+
circle_coordinates(*radius, *height_segments)
677700
.map(|p| Vec3::new(p.x, -half_height, p.y))
678701
.map(move |p| [p, end])
679702
.map(|ps| ps.map(rotate_then_translate_3d(*rotation, *position)))

0 commit comments

Comments
 (0)