1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use bevy::{input::mouse::MouseMotion, prelude::*, transform::TransformSystem};
5use std::f32::consts::{FRAC_PI_2, PI, TAU};
6
7pub struct SunGizmoPlugin {
9 pub position: Vec2,
11 pub size: f32,
13 pub persist_time: f32,
15 pub key_bindings: Vec<KeyCode>,
17 pub sensitivity: f32,
19 pub line_width: f32,
21}
22
23impl Default for SunGizmoPlugin {
24 fn default() -> Self {
25 Self {
26 position: Vec2::new(0.7, 0.7),
27 size: 0.1,
28 persist_time: 5.0,
29 key_bindings: vec![KeyCode::ControlRight, KeyCode::KeyL],
30 sensitivity: 0.5,
31 line_width: 4.0,
32 }
33 }
34}
35
36impl Plugin for SunGizmoPlugin {
37 fn build(&self, app: &mut App) {
38 app.add_systems(Update, update_sun)
39 .add_systems(
40 PostUpdate,
41 draw_sun_gizmo.after(TransformSystem::TransformPropagate),
42 )
43 .add_event::<SunUpdatedEvent>()
44 .insert_resource(SunGizmoControlConfig {
45 key_bindings: self.key_bindings.clone(),
46 sensitivity: self.sensitivity,
47 })
48 .insert_gizmo_group(
49 SunGizmoGroup {
50 pos: self.position,
51 size: self.size,
52 persist_time: self.persist_time,
53 },
54 GizmoConfig {
55 line_width: self.line_width,
56 depth_bias: -1.0,
57 ..default()
58 },
59 );
60 }
61}
62
63#[derive(Resource)]
64struct SunGizmoControlConfig {
65 pub key_bindings: Vec<KeyCode>,
66 pub sensitivity: f32,
67}
68
69#[derive(Default, Reflect, GizmoConfigGroup)]
70struct SunGizmoGroup {
71 pub pos: Vec2,
72 pub size: f32,
73 pub persist_time: f32,
74}
75
76#[derive(Event)]
77struct SunUpdatedEvent;
78
79fn update_sun(
80 mut query: Query<&mut Transform, With<DirectionalLight>>,
81 mut mouse: EventReader<MouseMotion>,
82 mut event_writer: EventWriter<SunUpdatedEvent>,
83 input: Res<ButtonInput<KeyCode>>,
84 config: Res<SunGizmoControlConfig>,
85 time: Res<Time>,
86) {
87 let Ok(mut transform) = query.get_single_mut() else {
88 return;
89 };
90
91 if input.all_pressed(config.key_bindings.clone()) {
92 let mut delta = Vec2::ZERO;
93 for event in mouse.read() {
94 delta += event.delta * config.sensitivity;
95 }
96
97 let target: Vec3 = transform.up().into();
98 let right: Vec3 = transform.right().into();
99 let forward: Vec3 = transform.forward().into();
100 let angle_between = forward.angle_between(target);
101 transform.rotate_axis(right, angle_between.min(-delta.y * time.delta_seconds()));
102 transform.rotate_axis(Vec3::Y, delta.x * time.delta_seconds());
103
104 event_writer.send(SunUpdatedEvent);
105 }
106}
107
108fn draw_sun_gizmo(
109 directional_light: Query<&mut Transform, With<DirectionalLight>>,
110 camera: Query<(&Camera, &GlobalTransform)>,
111 mut gizmos: Gizmos<SunGizmoGroup>,
112 mut event_reader: EventReader<SunUpdatedEvent>,
113 mut persist_time: Local<f32>,
114 time: Res<Time>,
115) {
116 if !event_reader.is_empty() {
117 *persist_time = gizmos.config_ext.persist_time;
118 event_reader.clear();
119 }
120
121 if *persist_time < 0.0 {
122 return;
123 }
124
125 *persist_time -= time.delta_seconds();
126
127 let Ok(directional_light) = directional_light.get_single() else {
128 return;
129 };
130 let Ok((camera, camera_transform)) = camera.get_single() else {
131 return;
132 };
133 let Some(viewport_size) = camera.logical_viewport_size() else {
134 return;
135 };
136
137 let viewport_pos = viewport_size * gizmos.config_ext.pos;
138
139 let Some(ray) = camera.viewport_to_world(camera_transform, viewport_pos) else {
140 return;
141 };
142
143 let size = gizmos.config_ext.size;
144 let origin = ray.get_point(1.0 / size);
145 let x_color = Color::rgba(1.0, 0.4, 0.4, 1.0);
146 let y_color = Color::rgba(0.4, 1.0, 0.4, 1.0);
147 let z_color = Color::rgba(0.4, 0.4, 1.0, 1.0);
148
149 let y_forward_roation = Quat::IDENTITY;
151 gizmos.arc_3d(TAU, 1.0, origin, y_forward_roation, y_color);
152 gizmos.arc_3d(TAU, 0.8, origin, y_forward_roation, y_color.with_a(0.4));
153 gizmos.arc_3d(TAU, 0.6, origin, y_forward_roation, y_color.with_a(0.25));
154 gizmos.arc_3d(TAU, 0.4, origin, y_forward_roation, y_color.with_a(0.1));
155
156 let x_forward_rotation =
158 Quat::from_rotation_arc(Vec3::Y, Vec3::X) * Quat::from_rotation_y(FRAC_PI_2);
159 gizmos.arc_3d(PI, 1.0, origin, x_forward_rotation, x_color);
160 let z_forward_rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::Z);
162 gizmos.arc_3d(PI, 1.0, origin, z_forward_rotation, z_color);
163
164 gizmos.arrow(origin, origin + Vec3::X, x_color);
165 gizmos.arrow(origin, origin + Vec3::Y, y_color);
166 gizmos.arrow(origin, origin + Vec3::Z, z_color);
167
168 let light_dir = *directional_light.forward();
169 let start = origin - light_dir * 1.2;
170 let end = origin - light_dir * 0.2;
171 gizmos.arrow(start, end, Color::YELLOW);
172
173 let projected_start = Vec3::new(start.x, origin.y, start.z);
174 let x_axis_proj = Vec3::new(start.x, origin.y, origin.z);
175 let z_axis_proj = Vec3::new(origin.x, origin.y, start.z);
176 gizmos.line(start, projected_start, y_color);
177 gizmos.line(projected_start, x_axis_proj, z_color);
178 gizmos.line(projected_start, z_axis_proj, x_color);
179}