eulumdat_bevy/viewer/
controls.rs1use super::scenes::SceneType;
4use super::ViewerSettings;
5use bevy::prelude::*;
6use eulumdat::Eulumdat;
7
8const DIMENSION_STEP: f32 = 0.5;
10const FINE_STEP: f32 = 0.1;
12const MIN_DIMENSION: f32 = 1.0;
14const MAX_DIMENSION: f32 = 50.0;
16const MIN_HEIGHT: f32 = 2.0;
18const MAX_HEIGHT: f32 = 20.0;
20const MAX_PENDULUM: f32 = 20.0;
22
23pub fn viewer_controls_system(
45 mut settings: ResMut<ViewerSettings>,
46 keyboard: Res<ButtonInput<KeyCode>>,
47) {
48 if keyboard.just_pressed(KeyCode::KeyP) {
50 settings.show_photometric_solid = !settings.show_photometric_solid;
51 }
52
53 if keyboard.just_pressed(KeyCode::KeyL) {
55 settings.show_luminaire = !settings.show_luminaire;
56 }
57
58 if keyboard.just_pressed(KeyCode::KeyH) {
60 settings.show_shadows = !settings.show_shadows;
61 }
62
63 if keyboard.just_pressed(KeyCode::Digit1) {
65 settings.scene_type = SceneType::Room;
66 }
67 if keyboard.just_pressed(KeyCode::Digit2) {
68 settings.scene_type = SceneType::Road;
69 }
70 if keyboard.just_pressed(KeyCode::Digit3) {
71 settings.scene_type = SceneType::Parking;
72 }
73 if keyboard.just_pressed(KeyCode::Digit4) {
74 settings.scene_type = SceneType::Outdoor;
75 }
76
77 if keyboard.just_pressed(KeyCode::BracketLeft) {
80 settings.room_width = (settings.room_width - DIMENSION_STEP).max(MIN_DIMENSION);
81 }
82 if keyboard.just_pressed(KeyCode::BracketRight) {
83 settings.room_width = (settings.room_width + DIMENSION_STEP).min(MAX_DIMENSION);
84 }
85
86 if keyboard.just_pressed(KeyCode::Minus) {
88 settings.room_length = (settings.room_length - DIMENSION_STEP).max(MIN_DIMENSION);
89 }
90 if keyboard.just_pressed(KeyCode::Equal) {
91 settings.room_length = (settings.room_length + DIMENSION_STEP).min(MAX_DIMENSION);
92 }
93
94 if keyboard.just_pressed(KeyCode::Digit9) {
96 settings.room_height = (settings.room_height - DIMENSION_STEP).max(MIN_HEIGHT);
97 if settings.mounting_height > settings.room_height - 0.1 {
99 settings.mounting_height = settings.room_height - 0.1;
100 }
101 }
102 if keyboard.just_pressed(KeyCode::Digit0) {
103 settings.room_height = (settings.room_height + DIMENSION_STEP).min(MAX_HEIGHT);
104 }
105
106 if keyboard.just_pressed(KeyCode::Semicolon) {
108 settings.pendulum_length = (settings.pendulum_length - FINE_STEP).max(0.0);
109 }
110 if keyboard.just_pressed(KeyCode::Quote) {
111 let max_pendulum = (settings.attachment_height() - 1.0).clamp(0.0, MAX_PENDULUM);
113 settings.pendulum_length = (settings.pendulum_length + FINE_STEP).min(max_pendulum);
114 }
115
116 if keyboard.just_pressed(KeyCode::Comma) {
118 settings.mounting_height = (settings.mounting_height - FINE_STEP).max(2.0);
119 }
120 if keyboard.just_pressed(KeyCode::Period) {
121 settings.mounting_height = (settings.mounting_height + FINE_STEP).min(MAX_HEIGHT);
122 }
123
124 const TILT_STEP: f32 = 5.0;
126 if keyboard.just_pressed(KeyCode::KeyT) {
127 settings.luminaire_tilt = (settings.luminaire_tilt - TILT_STEP).max(0.0);
128 }
129 if keyboard.just_pressed(KeyCode::KeyY) {
130 settings.luminaire_tilt = (settings.luminaire_tilt + TILT_STEP).min(90.0);
131 }
132}
133
134pub fn sync_viewer_to_lights(
141 mut commands: Commands,
142 settings: Res<ViewerSettings>,
143 lights: Query<(
144 Entity,
145 &crate::photometric::PhotometricLight<Eulumdat>,
146 &Transform,
147 )>,
148) {
149 if !settings.is_changed() {
150 return;
151 }
152
153 let ldt_data = lights.iter().next().map(|(_, l, _)| l.data.clone());
155 let Some(ldt) = ldt_data else {
156 return;
157 };
158
159 let transforms = calculate_all_luminaire_transforms(&settings, &ldt);
161 let current_count = lights.iter().count();
162 let required_count = transforms.len();
163
164 if current_count != required_count {
166 for (entity, _, _) in lights.iter() {
168 commands.entity(entity).despawn();
169 }
170
171 for transform in transforms {
173 commands.spawn(
174 crate::eulumdat_impl::EulumdatLightBundle::new(ldt.clone())
175 .with_transform(
176 Transform::from_translation(transform.position)
177 .with_rotation(transform.rotation),
178 )
179 .with_solid(settings.show_photometric_solid)
180 .with_model(settings.show_luminaire)
181 .with_shadows(settings.show_shadows),
182 );
183 }
184 } else {
185 for (idx, (entity, light, _)) in lights.iter().enumerate() {
187 if let Some(lt) = transforms.get(idx) {
188 let mut updated_light =
189 crate::photometric::PhotometricLight::new(light.data.clone());
190 updated_light.show_solid = settings.show_photometric_solid;
191 updated_light.show_model = settings.show_luminaire;
192 updated_light.shadows_enabled = settings.show_shadows;
193 updated_light.intensity_scale = light.intensity_scale;
194
195 commands.entity(entity).insert((
196 Transform::from_translation(lt.position).with_rotation(lt.rotation),
197 updated_light,
198 ));
199 }
200 }
201 }
202}
203
204#[derive(Clone, Copy)]
206pub struct LuminaireTransform {
207 pub position: Vec3,
208 pub rotation: Quat,
209}
210
211pub fn calculate_all_luminaire_transforms(
214 settings: &ViewerSettings,
215 ldt: &Eulumdat,
216) -> Vec<LuminaireTransform> {
217 let y = settings.luminaire_height(ldt);
218
219 match settings.scene_type {
220 SceneType::Room => {
221 vec![LuminaireTransform {
223 position: Vec3::new(settings.room_width / 2.0, y, settings.room_length / 2.0),
224 rotation: Quat::IDENTITY,
225 }]
226 }
227 SceneType::Road => calculate_road_luminaires(settings, y),
228 SceneType::Parking | SceneType::Outdoor => {
229 vec![LuminaireTransform {
231 position: Vec3::new(
232 settings.room_width / 2.0 - 0.2,
233 y,
234 settings.room_length / 2.0,
235 ),
236 rotation: Quat::IDENTITY,
237 }]
238 }
239 }
240}
241
242fn calculate_road_luminaires(settings: &ViewerSettings, y: f32) -> Vec<LuminaireTransform> {
247 let lane_w = settings.lane_width;
248 let num_lanes = settings.num_lanes;
249 let sidewalk_w = settings.sidewalk_width;
250 let road_width = num_lanes as f32 * lane_w;
251 let total_width = road_width + 2.0 * sidewalk_w;
252 let road_length = settings.room_length;
253 let pole_spacing = settings.effective_pole_spacing();
254
255 let num_poles = ((road_length / pole_spacing).floor() as i32).max(1);
257 let actual_spacing = road_length / (num_poles as f32 + 1.0);
258
259 let ratio = road_width / settings.mounting_height;
261 let tilt = settings.luminaire_tilt.to_radians();
262
263 let arm_length = 1.5;
265
266 let mut transforms = Vec::new();
267
268 let middle_pole_spacing = 50.0;
270 let center_x = sidewalk_w + road_width / 2.0;
271
272 if ratio < 1.0 {
273 let rotation = Quat::from_rotation_z(tilt);
276 for i in 1..=num_poles {
277 let z = i as f32 * actual_spacing;
278 transforms.push(LuminaireTransform {
279 position: Vec3::new(total_width - sidewalk_w / 2.0 - arm_length, y, z),
280 rotation,
281 });
282 }
283 } else if ratio < 1.5 {
284 for i in 1..=num_poles {
286 let z = i as f32 * actual_spacing;
287 if i % 2 == 0 {
288 transforms.push(LuminaireTransform {
290 position: Vec3::new(sidewalk_w / 2.0 + arm_length, y, z),
291 rotation: Quat::from_rotation_z(-tilt),
292 });
293 } else {
294 transforms.push(LuminaireTransform {
296 position: Vec3::new(total_width - sidewalk_w / 2.0 - arm_length, y, z),
297 rotation: Quat::from_rotation_z(tilt),
298 });
299 }
300 }
301 } else {
302 for i in 1..=num_poles {
305 let z = i as f32 * actual_spacing;
306 transforms.push(LuminaireTransform {
308 position: Vec3::new(sidewalk_w / 2.0 + arm_length, y, z),
309 rotation: Quat::from_rotation_z(-tilt),
310 });
311 transforms.push(LuminaireTransform {
313 position: Vec3::new(total_width - sidewalk_w / 2.0 - arm_length, y, z),
314 rotation: Quat::from_rotation_z(tilt),
315 });
316 }
317
318 if road_width > 6.0 {
320 let num_middle_poles = ((road_length / middle_pole_spacing).floor() as i32).max(0);
321 for i in 1..=num_middle_poles {
322 let z = i as f32 * middle_pole_spacing;
323 transforms.push(LuminaireTransform {
326 position: Vec3::new(center_x - 1.0, y, z),
327 rotation: Quat::from_rotation_z(-tilt * 0.5), });
329 transforms.push(LuminaireTransform {
331 position: Vec3::new(center_x + 1.0, y, z),
332 rotation: Quat::from_rotation_z(tilt * 0.5),
333 });
334 }
335 }
336 }
337
338 transforms
339}
340
341pub fn calculate_light_position(settings: &ViewerSettings, ldt: &Eulumdat) -> Vec3 {
345 let transforms = calculate_all_luminaire_transforms(settings, ldt);
346 transforms.first().map(|t| t.position).unwrap_or(Vec3::ZERO)
347}
348
349pub fn calculate_light_rotation(settings: &ViewerSettings) -> Quat {
355 match settings.scene_type {
356 SceneType::Room => Quat::IDENTITY, SceneType::Road => {
358 let tilt_angle = -settings.luminaire_tilt.to_radians();
364 Quat::from_rotation_z(tilt_angle)
365 }
366 SceneType::Parking => Quat::IDENTITY, SceneType::Outdoor => Quat::IDENTITY, }
369}