Skip to content

Commit de0ed29

Browse files
KanabenkiMrGVSV
andauthored
Add basic light gizmos (#12228)
# Objective - Part of #9400. - Add light gizmos for `SpotLight`, `PointLight` and `DirectionalLight`. ## Solution - Add a `ShowLightGizmo` and its related gizmo group and plugin, that shows a gizmo for all lights of an entities when inserted on it. Light display can also be toggled globally through the gizmo config in the same way it can already be done for `Aabb`s. - Add distinct segment setters for height and base one `Cone3dBuilder`. This allow having a properly rounded base without too much edges along the height. The doc comments explain how to ensure height and base connect when setting different values. Gizmo for the three light types without radius with the depth bias set to -1: ![without-radius](https://github.com/bevyengine/bevy/assets/18357657/699d0154-f367-4727-9b09-8b458d96a0e2) With Radius: ![with-radius](https://github.com/bevyengine/bevy/assets/18357657/f3af003e-dbba-427a-a305-c5cc1676e340) Possible future improvements: - Add a billboarded sprite with a distinct sprite for each light type. - Display the intensity of the light somehow (no idea how to represent that apart from some text). --- ## Changelog ### Added - The new `ShowLightGizmo`, part of the `LightGizmoPlugin` and configurable globally with `LightGizmoConfigGroup`, allows drawing gizmo for `PointLight`, `SpotLight` and `DirectionalLight`. The gizmos color behavior can be controlled with the `LightGizmoColor` member of `ShowLightGizmo` and `LightGizmoConfigGroup`. - The cone gizmo builder (`Cone3dBuilder`) now allows setting a differing number of segments for the base and height. --------- Co-authored-by: Gino Valente <[email protected]>
1 parent 6032978 commit de0ed29

File tree

6 files changed

+563
-9
lines changed

6 files changed

+563
-9
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,6 +2680,17 @@ description = "A scene showcasing 3D gizmos"
26802680
category = "Gizmos"
26812681
wasm = true
26822682

2683+
[[example]]
2684+
name = "light_gizmos"
2685+
path = "examples/gizmos/light_gizmos.rs"
2686+
doc-scrape-examples = true
2687+
2688+
[package.metadata.example.light_gizmos]
2689+
name = "Light Gizmos"
2690+
description = "A scene showcasing light gizmos"
2691+
category = "Gizmos"
2692+
wasm = true
2693+
26832694
[profile.wasm-release]
26842695
inherits = "release"
26852696
opt-level = "z"

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")]
@@ -46,6 +47,7 @@ pub mod prelude {
4647
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
4748
config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore},
4849
gizmos::Gizmos,
50+
light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo},
4951
primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
5052
AppGizmoBuilder,
5153
};
@@ -85,6 +87,7 @@ use config::{
8587
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig,
8688
};
8789
use gizmos::GizmoStorage;
90+
use light::LightGizmoPlugin;
8891
use std::{any::TypeId, mem};
8992

9093
const LINE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(7414812689238026784);
@@ -110,7 +113,8 @@ impl Plugin for GizmoPlugin {
110113
.init_resource::<LineGizmoHandles>()
111114
// We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
112115
.init_gizmo_group::<DefaultGizmoConfigGroup>()
113-
.add_plugins(AabbGizmoPlugin);
116+
.add_plugins(AabbGizmoPlugin)
117+
.add_plugins(LightGizmoPlugin);
114118

115119
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
116120
return;

crates/bevy_gizmos/src/light.rs

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
//! A module adding debug visualization of [`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::{
9+
palettes::basic::{BLUE, GREEN, RED},
10+
Color, Oklcha,
11+
};
12+
use bevy_ecs::{
13+
component::Component,
14+
entity::Entity,
15+
query::Without,
16+
reflect::ReflectComponent,
17+
schedule::IntoSystemConfigs,
18+
system::{Query, Res},
19+
};
20+
use bevy_math::{
21+
primitives::{Cone, Sphere},
22+
Quat, Vec3,
23+
};
24+
use bevy_pbr::{DirectionalLight, PointLight, SpotLight};
25+
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
26+
use bevy_transform::{components::GlobalTransform, TransformSystem};
27+
28+
use crate::{
29+
config::{GizmoConfigGroup, GizmoConfigStore},
30+
gizmos::Gizmos,
31+
AppGizmoBuilder,
32+
};
33+
34+
/// Draws a standard sphere for the radius and an axis sphere for the range.
35+
fn point_light_gizmo(
36+
transform: &GlobalTransform,
37+
point_light: &PointLight,
38+
color: Color,
39+
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
40+
) {
41+
let position = transform.translation();
42+
gizmos
43+
.primitive_3d(
44+
Sphere {
45+
radius: point_light.radius,
46+
},
47+
position,
48+
Quat::IDENTITY,
49+
color,
50+
)
51+
.segments(16);
52+
gizmos
53+
.sphere(position, Quat::IDENTITY, point_light.range, color)
54+
.circle_segments(32);
55+
}
56+
57+
/// Draws a sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the
58+
/// farthest point of effect of the spot light along its direction.
59+
fn spot_light_gizmo(
60+
transform: &GlobalTransform,
61+
spot_light: &SpotLight,
62+
color: Color,
63+
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
64+
) {
65+
let (_, rotation, translation) = transform.to_scale_rotation_translation();
66+
gizmos
67+
.primitive_3d(
68+
Sphere {
69+
radius: spot_light.radius,
70+
},
71+
translation,
72+
Quat::IDENTITY,
73+
color,
74+
)
75+
.segments(16);
76+
77+
// Offset the tip of the cone to the light position.
78+
for angle in [spot_light.inner_angle, spot_light.outer_angle] {
79+
let height = spot_light.range * angle.cos();
80+
let position = translation + rotation * Vec3::NEG_Z * height / 2.0;
81+
gizmos
82+
.primitive_3d(
83+
Cone {
84+
radius: spot_light.range * angle.sin(),
85+
height,
86+
},
87+
position,
88+
rotation * Quat::from_rotation_x(PI / 2.0),
89+
color,
90+
)
91+
.height_segments(4)
92+
.base_segments(32);
93+
}
94+
95+
for arc_rotation in [
96+
Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle),
97+
Quat::from_euler(
98+
bevy_math::EulerRot::XZY,
99+
0.0,
100+
PI / 2.0,
101+
PI / 2.0 - spot_light.outer_angle,
102+
),
103+
] {
104+
gizmos
105+
.arc_3d(
106+
2.0 * spot_light.outer_angle,
107+
spot_light.range,
108+
translation,
109+
rotation * arc_rotation,
110+
color,
111+
)
112+
.segments(16);
113+
}
114+
}
115+
116+
/// Draws an arrow alongside the directional light direction.
117+
fn directional_light_gizmo(
118+
transform: &GlobalTransform,
119+
color: Color,
120+
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
121+
) {
122+
let (_, rotation, translation) = transform.to_scale_rotation_translation();
123+
gizmos
124+
.arrow(translation, translation + rotation * Vec3::NEG_Z, color)
125+
.with_tip_length(0.3);
126+
}
127+
128+
/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s
129+
/// and [`DirectionalLight`]s for debugging.
130+
pub struct LightGizmoPlugin;
131+
132+
impl Plugin for LightGizmoPlugin {
133+
fn build(&self, app: &mut bevy_app::App) {
134+
app.register_type::<LightGizmoConfigGroup>()
135+
.init_gizmo_group::<LightGizmoConfigGroup>()
136+
.add_systems(
137+
PostUpdate,
138+
(
139+
draw_lights,
140+
draw_all_lights.run_if(|config: Res<GizmoConfigStore>| {
141+
config.config::<LightGizmoConfigGroup>().1.draw_all
142+
}),
143+
)
144+
.after(TransformSystem::TransformPropagate),
145+
);
146+
}
147+
}
148+
149+
/// Configures how a color is attributed to a light gizmo.
150+
#[derive(Debug, Clone, Copy, Default, Reflect)]
151+
pub enum LightGizmoColor {
152+
/// User-specified color.
153+
Manual(Color),
154+
/// Random color derived from the light's [`Entity`].
155+
Varied,
156+
/// Take the color of the represented light.
157+
#[default]
158+
MatchLightColor,
159+
/// Take the color provided by [`LightGizmoConfigGroup`] depending on the light kind.
160+
ByLightType,
161+
}
162+
163+
/// The [`GizmoConfigGroup`] used to configure the visualization of lights.
164+
#[derive(Clone, Reflect, GizmoConfigGroup)]
165+
pub struct LightGizmoConfigGroup {
166+
/// Draw a gizmo for all lights if true.
167+
///
168+
/// Defaults to `false`.
169+
pub draw_all: bool,
170+
/// Default color strategy for all light gizmos.
171+
///
172+
/// Defaults to [`LightGizmoColor::MatchLightColor`].
173+
pub color: LightGizmoColor,
174+
/// [`Color`] to use for drawing a [`PointLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
175+
///
176+
/// Defaults to [`RED`].
177+
pub point_light_color: Color,
178+
/// [`Color`] to use for drawing a [`SpotLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
179+
///
180+
/// Defaults to [`GREEN`].
181+
pub spot_light_color: Color,
182+
/// [`Color`] to use for drawing a [`DirectionalLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
183+
///
184+
/// Defaults to [`BLUE`].
185+
pub directional_light_color: Color,
186+
}
187+
188+
impl Default for LightGizmoConfigGroup {
189+
fn default() -> Self {
190+
Self {
191+
draw_all: false,
192+
color: LightGizmoColor::MatchLightColor,
193+
point_light_color: RED.into(),
194+
spot_light_color: GREEN.into(),
195+
directional_light_color: BLUE.into(),
196+
}
197+
}
198+
}
199+
200+
/// Add this [`Component`] to an entity to draw any of its lights components
201+
/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]).
202+
#[derive(Component, Reflect, Default, Debug)]
203+
#[reflect(Component, Default)]
204+
pub struct ShowLightGizmo {
205+
/// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`].
206+
///
207+
/// Defaults to [`None`].
208+
pub color: Option<LightGizmoColor>,
209+
}
210+
211+
fn draw_lights(
212+
point_query: Query<(Entity, &PointLight, &GlobalTransform, &ShowLightGizmo)>,
213+
spot_query: Query<(Entity, &SpotLight, &GlobalTransform, &ShowLightGizmo)>,
214+
directional_query: Query<(Entity, &DirectionalLight, &GlobalTransform, &ShowLightGizmo)>,
215+
mut gizmos: Gizmos<LightGizmoConfigGroup>,
216+
) {
217+
let color = |entity: Entity, gizmo_color: Option<LightGizmoColor>, light_color, type_color| {
218+
match gizmo_color.unwrap_or(gizmos.config_ext.color) {
219+
LightGizmoColor::Manual(color) => color,
220+
LightGizmoColor::Varied => Oklcha::sequential_dispersed(entity.index()).into(),
221+
LightGizmoColor::MatchLightColor => light_color,
222+
LightGizmoColor::ByLightType => type_color,
223+
}
224+
};
225+
for (entity, light, transform, light_gizmo) in &point_query {
226+
let color = color(
227+
entity,
228+
light_gizmo.color,
229+
light.color,
230+
gizmos.config_ext.point_light_color,
231+
);
232+
point_light_gizmo(transform, light, color, &mut gizmos);
233+
}
234+
for (entity, light, transform, light_gizmo) in &spot_query {
235+
let color = color(
236+
entity,
237+
light_gizmo.color,
238+
light.color,
239+
gizmos.config_ext.spot_light_color,
240+
);
241+
spot_light_gizmo(transform, light, color, &mut gizmos);
242+
}
243+
for (entity, light, transform, light_gizmo) in &directional_query {
244+
let color = color(
245+
entity,
246+
light_gizmo.color,
247+
light.color,
248+
gizmos.config_ext.directional_light_color,
249+
);
250+
directional_light_gizmo(transform, color, &mut gizmos);
251+
}
252+
}
253+
254+
fn draw_all_lights(
255+
point_query: Query<(Entity, &PointLight, &GlobalTransform), Without<ShowLightGizmo>>,
256+
spot_query: Query<(Entity, &SpotLight, &GlobalTransform), Without<ShowLightGizmo>>,
257+
directional_query: Query<
258+
(Entity, &DirectionalLight, &GlobalTransform),
259+
Without<ShowLightGizmo>,
260+
>,
261+
mut gizmos: Gizmos<LightGizmoConfigGroup>,
262+
) {
263+
match gizmos.config_ext.color {
264+
LightGizmoColor::Manual(color) => {
265+
for (_, light, transform) in &point_query {
266+
point_light_gizmo(transform, light, color, &mut gizmos);
267+
}
268+
for (_, light, transform) in &spot_query {
269+
spot_light_gizmo(transform, light, color, &mut gizmos);
270+
}
271+
for (_, _, transform) in &directional_query {
272+
directional_light_gizmo(transform, color, &mut gizmos);
273+
}
274+
}
275+
LightGizmoColor::Varied => {
276+
let color = |entity: Entity| Oklcha::sequential_dispersed(entity.index()).into();
277+
for (entity, light, transform) in &point_query {
278+
point_light_gizmo(transform, light, color(entity), &mut gizmos);
279+
}
280+
for (entity, light, transform) in &spot_query {
281+
spot_light_gizmo(transform, light, color(entity), &mut gizmos);
282+
}
283+
for (entity, _, transform) in &directional_query {
284+
directional_light_gizmo(transform, color(entity), &mut gizmos);
285+
}
286+
}
287+
LightGizmoColor::MatchLightColor => {
288+
for (_, light, transform) in &point_query {
289+
point_light_gizmo(transform, light, light.color, &mut gizmos);
290+
}
291+
for (_, light, transform) in &spot_query {
292+
spot_light_gizmo(transform, light, light.color, &mut gizmos);
293+
}
294+
for (_, light, transform) in &directional_query {
295+
directional_light_gizmo(transform, light.color, &mut gizmos);
296+
}
297+
}
298+
LightGizmoColor::ByLightType => {
299+
for (_, light, transform) in &point_query {
300+
point_light_gizmo(
301+
transform,
302+
light,
303+
gizmos.config_ext.point_light_color,
304+
&mut gizmos,
305+
);
306+
}
307+
for (_, light, transform) in &spot_query {
308+
spot_light_gizmo(
309+
transform,
310+
light,
311+
gizmos.config_ext.spot_light_color,
312+
&mut gizmos,
313+
);
314+
}
315+
for (_, _, transform) in &directional_query {
316+
directional_light_gizmo(
317+
transform,
318+
gizmos.config_ext.directional_light_color,
319+
&mut gizmos,
320+
);
321+
}
322+
}
323+
}
324+
}

0 commit comments

Comments
 (0)