bevy/crates/bevy_gizmos/src/light.rs
Robert Walter 8895113784
Use Isometry in bevy_gizmos wherever we can (#14676)
# Objective

- Solves the last bullet in and closes #14319
- Make better use of the `Isometry` types
- Prevent issues like #14655
- Probably simplify and clean up a lot of code through the use of Gizmos
as well (i.e. the 3D gizmos for cylinders circles & lines don't connect
well, probably due to wrong rotations)

## Solution

- go through the `bevy_gizmos` crate and give all methods a slight
workover

## Testing

- For all the changed examples I run `git switch main && cargo rr
--example <X> && git switch <BRANCH> && cargo rr --example <X>` and
compare the visual results
- Check if all doc tests are still compiling
- Check the docs in general and update them !!! 

---

## Migration Guide

The gizmos methods function signature changes as follows:

- 2D
- if it took `position` & `rotation_angle` before ->
`Isometry2d::new(position, Rot2::radians(rotation_angle))`
- if it just took `position` before ->
`Isometry2d::from_translation(position)`
- 3D
- if it took `position` & `rotation` before ->
`Isometry3d::new(position, rotation)`
- if it just took `position` before ->
`Isometry3d::from_translation(position)`
2024-08-28 01:37:19 +00:00

325 lines
11 KiB
Rust

//! A module adding debug visualization of [`PointLight`]s, [`SpotLight`]s and [`DirectionalLight`]s.
use std::f32::consts::PI;
use crate::{self as bevy_gizmos, primitives::dim3::GizmoPrimitive3d};
use bevy_app::{Plugin, PostUpdate};
use bevy_color::{
palettes::basic::{BLUE, GREEN, RED},
Color, Oklcha,
};
use bevy_ecs::{
component::Component,
entity::Entity,
query::Without,
reflect::ReflectComponent,
schedule::IntoSystemConfigs,
system::{Query, Res},
};
use bevy_math::{
primitives::{Cone, Sphere},
Isometry3d, Quat, Vec3,
};
use bevy_pbr::{DirectionalLight, PointLight, SpotLight};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::{components::GlobalTransform, TransformSystem};
use crate::{
config::{GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
AppGizmoBuilder,
};
/// Draws a standard sphere for the radius and an axis sphere for the range.
fn point_light_gizmo(
transform: &GlobalTransform,
point_light: &PointLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let position = transform.translation();
gizmos
.primitive_3d(
&Sphere {
radius: point_light.radius,
},
Isometry3d::from_translation(position),
color,
)
.resolution(16);
gizmos
.sphere(
Isometry3d::from_translation(position),
point_light.range,
color,
)
.resolution(32);
}
/// Draws a sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the
/// farthest point of effect of the spot light along its direction.
fn spot_light_gizmo(
transform: &GlobalTransform,
spot_light: &SpotLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.primitive_3d(
&Sphere {
radius: spot_light.radius,
},
Isometry3d::from_translation(translation),
color,
)
.resolution(16);
// Offset the tip of the cone to the light position.
for angle in [spot_light.inner_angle, spot_light.outer_angle] {
let height = spot_light.range * angle.cos();
let position = translation + rotation * Vec3::NEG_Z * height / 2.0;
gizmos
.primitive_3d(
&Cone {
radius: spot_light.range * angle.sin(),
height,
},
Isometry3d::new(position, rotation * Quat::from_rotation_x(PI / 2.0)),
color,
)
.height_resolution(4)
.base_resolution(32);
}
for arc_rotation in [
Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle),
Quat::from_euler(
bevy_math::EulerRot::XZY,
0.0,
PI / 2.0,
PI / 2.0 - spot_light.outer_angle,
),
] {
gizmos
.arc_3d(
2.0 * spot_light.outer_angle,
spot_light.range,
Isometry3d::new(translation, rotation * arc_rotation),
color,
)
.resolution(16);
}
}
/// Draws an arrow alongside the directional light direction.
fn directional_light_gizmo(
transform: &GlobalTransform,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.arrow(translation, translation + rotation * Vec3::NEG_Z, color)
.with_tip_length(0.3);
}
/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s
/// and [`DirectionalLight`]s for debugging.
pub struct LightGizmoPlugin;
impl Plugin for LightGizmoPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.register_type::<LightGizmoConfigGroup>()
.init_gizmo_group::<LightGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_lights,
draw_all_lights.run_if(|config: Res<GizmoConfigStore>| {
config.config::<LightGizmoConfigGroup>().1.draw_all
}),
)
.after(TransformSystem::TransformPropagate),
);
}
}
/// Configures how a color is attributed to a light gizmo.
#[derive(Debug, Clone, Copy, Default, Reflect)]
pub enum LightGizmoColor {
/// User-specified color.
Manual(Color),
/// Random color derived from the light's [`Entity`].
Varied,
/// Take the color of the represented light.
#[default]
MatchLightColor,
/// Take the color provided by [`LightGizmoConfigGroup`] depending on the light kind.
ByLightType,
}
/// The [`GizmoConfigGroup`] used to configure the visualization of lights.
#[derive(Clone, Reflect, GizmoConfigGroup)]
pub struct LightGizmoConfigGroup {
/// Draw a gizmo for all lights if true.
///
/// Defaults to `false`.
pub draw_all: bool,
/// Default color strategy for all light gizmos.
///
/// Defaults to [`LightGizmoColor::MatchLightColor`].
pub color: LightGizmoColor,
/// [`Color`] to use for drawing a [`PointLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`RED`].
pub point_light_color: Color,
/// [`Color`] to use for drawing a [`SpotLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`GREEN`].
pub spot_light_color: Color,
/// [`Color`] to use for drawing a [`DirectionalLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`BLUE`].
pub directional_light_color: Color,
}
impl Default for LightGizmoConfigGroup {
fn default() -> Self {
Self {
draw_all: false,
color: LightGizmoColor::MatchLightColor,
point_light_color: RED.into(),
spot_light_color: GREEN.into(),
directional_light_color: BLUE.into(),
}
}
}
/// Add this [`Component`] to an entity to draw any of its lights components
/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]).
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component, Default)]
pub struct ShowLightGizmo {
/// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`].
///
/// Defaults to [`None`].
pub color: Option<LightGizmoColor>,
}
fn draw_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform, &ShowLightGizmo)>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform, &ShowLightGizmo)>,
directional_query: Query<(Entity, &DirectionalLight, &GlobalTransform, &ShowLightGizmo)>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
let color = |entity: Entity, gizmo_color: Option<LightGizmoColor>, light_color, type_color| {
match gizmo_color.unwrap_or(gizmos.config_ext.color) {
LightGizmoColor::Manual(color) => color,
LightGizmoColor::Varied => Oklcha::sequential_dispersed(entity.index()).into(),
LightGizmoColor::MatchLightColor => light_color,
LightGizmoColor::ByLightType => type_color,
}
};
for (entity, light, transform, light_gizmo) in &point_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.point_light_color,
);
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &spot_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.spot_light_color,
);
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &directional_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.directional_light_color,
);
directional_light_gizmo(transform, color, &mut gizmos);
}
}
fn draw_all_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform), Without<ShowLightGizmo>>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform), Without<ShowLightGizmo>>,
directional_query: Query<
(Entity, &DirectionalLight, &GlobalTransform),
Without<ShowLightGizmo>,
>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
match gizmos.config_ext.color {
LightGizmoColor::Manual(color) => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(transform, color, &mut gizmos);
}
}
LightGizmoColor::Varied => {
let color = |entity: Entity| Oklcha::sequential_dispersed(entity.index()).into();
for (entity, light, transform) in &point_query {
point_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, _, transform) in &directional_query {
directional_light_gizmo(transform, color(entity), &mut gizmos);
}
}
LightGizmoColor::MatchLightColor => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &directional_query {
directional_light_gizmo(transform, light.color, &mut gizmos);
}
}
LightGizmoColor::ByLightType => {
for (_, light, transform) in &point_query {
point_light_gizmo(
transform,
light,
gizmos.config_ext.point_light_color,
&mut gizmos,
);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(
transform,
light,
gizmos.config_ext.spot_light_color,
&mut gizmos,
);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(
transform,
gizmos.config_ext.directional_light_color,
&mut gizmos,
);
}
}
}
}