1use crate::{Value, ValueContainer};
2use theframework::prelude::*;
3use vek::{Vec2, Vec3};
4
5#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
7pub enum LightType {
8 Point,
9 Ambient,
10 AmbientDaylight,
11 Spot,
12 Area,
13 Daylight,
14}
15
16impl LightType {
17 pub fn name(&self) -> &'static str {
19 match self {
20 LightType::Point => "Point",
21 LightType::Ambient => "Ambient",
22 LightType::AmbientDaylight => "Ambient Daylight",
23 LightType::Spot => "Spot",
24 LightType::Area => "Area",
25 LightType::Daylight => "Daylight",
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct Light {
32 pub light_type: LightType,
33 pub properties: ValueContainer,
34 pub active: bool,
35}
36
37impl Light {
38 pub fn new(light_type: LightType) -> Self {
39 Self {
40 light_type,
41 properties: ValueContainer::default(),
42 active: true,
43 }
44 }
45
46 pub fn with_position(mut self, position: Vec3<f32>) -> Self {
48 self.set_position(position);
49 self
50 }
51
52 pub fn with_color(mut self, color: [f32; 3]) -> Self {
54 self.set_color(color);
55 self
56 }
57
58 pub fn with_intensity(mut self, intensity: f32) -> Self {
60 self.set_intensity(intensity);
61 self
62 }
63
64 pub fn with_start_distance(mut self, start: f32) -> Self {
66 self.set_start_distance(start);
67 self
68 }
69
70 pub fn with_end_distance(mut self, end: f32) -> Self {
72 self.set_end_distance(end);
73 self
74 }
75
76 pub fn with_flicker(mut self, flicker: f32) -> Self {
78 self.set_flicker(flicker);
79 self
80 }
81
82 fn get_position(&self) -> Vec3<f32> {
84 let p = self
85 .properties
86 .get_vec3("position")
87 .unwrap_or([0.0, 0.0, 0.0]);
88 Vec3::new(p[0], p[1], p[2])
89 }
90
91 pub fn get_color(&self) -> [f32; 3] {
93 self.properties.get_vec3("color").unwrap_or([1.0, 1.0, 1.0])
94 }
95
96 pub fn get_intensity(&self) -> f32 {
98 self.properties.get_float_default("intensity", 1.0)
99 }
100
101 pub fn get_start_distance(&self) -> f32 {
103 self.properties.get_float_default("start_distance", 1.0)
104 }
105
106 pub fn get_end_distance(&self) -> f32 {
108 self.properties.get_float_default("end_distance", 2.0)
109 }
110
111 pub fn get_flicker(&self) -> f32 {
113 self.properties.get_float_default("flicker", 0.0)
114 }
115
116 pub fn position(&self) -> Vec3<f32> {
118 self.get_position()
119 }
120
121 pub fn position_2d(&self) -> Vec2<f32> {
123 let p = self.position();
124 Vec2::new(p.x, p.z)
125 }
126
127 pub fn compile(&self) -> CompiledLight {
129 let position = {
131 let p = self
132 .properties
133 .get_vec3("position")
134 .unwrap_or([0.0, 0.0, 0.0]);
135 Vec3::new(p[0], p[1], p[2])
136 };
137 let color = self.properties.get_vec3("color").unwrap_or([1.0, 1.0, 1.0]);
138 let intensity = self.properties.get_float_default("intensity", 1.0);
139
140 let start_distance = self.properties.get_float_default("start_distance", 1.0);
142 let end_distance = self.properties.get_float_default("end_distance", 2.0);
143
144 let flicker = self.properties.get_float_default("flicker", 0.0);
145
146 let direction = {
148 let d = self
149 .properties
150 .get_vec3("direction")
151 .unwrap_or([0.0, 0.0, -1.0]);
152 Vec3::new(d[0], d[1], d[2]).normalized()
153 };
154 let cone_angle = self
155 .properties
156 .get_float_default("cone_angle", std::f32::consts::FRAC_PI_4);
157
158 let normal = {
160 let n = self
161 .properties
162 .get_vec3("normal")
163 .unwrap_or([0.0, 1.0, 0.0]);
164 Vec3::new(n[0], n[1], n[2]).normalized()
165 };
166 let width = self.properties.get_float_default("width", 1.0);
167 let height = self.properties.get_float_default("height", 1.0);
168 let emitting = self.properties.get_bool_default("emitting", true);
169
170 let from_linedef = self.properties.get_bool_default("from_linedef", false);
171
172 CompiledLight {
173 light_type: self.light_type,
174 position,
176 color,
177 intensity,
178 emitting,
179 start_distance,
181 end_distance,
182 flicker,
183 direction,
185 cone_angle,
186 normal,
188 width,
189 height,
190
191 from_linedef,
192 }
193 }
194
195 pub fn set_position(&mut self, position: Vec3<f32>) {
197 self.properties.set(
198 "position",
199 Value::Vec3([position.x, position.y, position.z]),
200 );
201 }
202
203 pub fn set_color(&mut self, new_color: [f32; 3]) {
205 self.properties.set("color", Value::Vec3(new_color));
206 }
207
208 pub fn set_intensity(&mut self, new_intensity: f32) {
210 self.properties
211 .set("intensity", Value::Float(new_intensity));
212 }
213
214 pub fn set_start_distance(&mut self, new_start_distance: f32) {
216 self.properties
217 .set("start_distance", Value::Float(new_start_distance));
218 }
219
220 pub fn set_end_distance(&mut self, new_end_distance: f32) {
222 self.properties
223 .set("end_distance", Value::Float(new_end_distance));
224 }
225
226 pub fn set_flicker(&mut self, flicker: f32) {
228 self.properties.set("flicker", Value::Float(flicker));
229 }
230
231 pub fn from_linedef(&self, p1: Vec2<f32>, p2: Vec2<f32>, height: f32) -> Self {
233 let position = (p1 + p2) / 2.0; let direction = (p2 - p1).normalized(); let normal = Vec2::new(direction.y, -direction.x); let width = (p2 - p1).magnitude(); let offset = 0.1;
238 let position = position + normal * offset;
239
240 match self.light_type {
241 LightType::Point => {
242 let mut light = Light::new(LightType::Point);
243 light.set_position(Vec3::new(position.x, height, position.y));
244
245 if let Some(start_distance) = self.properties.get("start_distance") {
246 light
247 .properties
248 .set("start_distance", start_distance.clone());
249 }
250 if let Some(end_distance) = self.properties.get("end_distance") {
251 light.properties.set("end_distance", end_distance.clone());
252 }
253 if let Some(intensity) = self.properties.get("intensity") {
254 light.properties.set("intensity", intensity.clone());
255 }
256 if let Some(color) = self.properties.get("color") {
257 light.properties.set("color", color.clone());
258 }
259
260 light
261 }
262 LightType::Ambient | LightType::AmbientDaylight => self.clone(),
263 LightType::Spot => {
264 let mut light = Light::new(LightType::Spot);
265 light.set_position(Vec3::new(position.x, height, position.y));
266
267 light
268 .properties
269 .set("direction", Value::Vec3([normal.x, 0.0, normal.y]));
270
271 if let Some(cone_angle) = self.properties.get("cone_angle") {
272 light.properties.set("cone_angle", cone_angle.clone());
273 }
274 if let Some(intensity) = self.properties.get("intensity") {
275 light.properties.set("intensity", intensity.clone());
276 }
277 if let Some(color) = self.properties.get("color") {
278 light.properties.set("color", color.clone());
279 }
280 if let Some(start_distance) = self.properties.get("start_distance") {
281 light
282 .properties
283 .set("start_distance", start_distance.clone());
284 }
285 if let Some(end_distance) = self.properties.get("end_distance") {
286 light.properties.set("end_distance", end_distance.clone());
287 }
288
289 light
290 }
291 LightType::Area => {
292 let mut light = Light::new(LightType::Area);
293 light.properties.set("from_linedef", Value::Bool(true));
294 light.set_position(Vec3::new(position.x, height, position.y));
295
296 light
297 .properties
298 .set("normal", Value::Vec3([normal.x, 0.0, normal.y]));
299
300 light.properties.set("width", Value::Float(width));
302 light.properties.set("height", Value::Float(1.0));
303
304 if let Some(intensity) = self.properties.get("intensity") {
305 light.properties.set("intensity", intensity.clone());
306 }
307 if let Some(color) = self.properties.get("color") {
308 light.properties.set("color", color.clone());
309 }
310 if let Some(start_distance) = self.properties.get("start_distance") {
311 light
312 .properties
313 .set("start_distance", start_distance.clone());
314 }
315 if let Some(end_distance) = self.properties.get("end_distance") {
316 light.properties.set("end_distance", end_distance.clone());
317 }
318
319 light
320 }
321 LightType::Daylight => {
322 let mut light = Light::new(LightType::Area);
323 light.set_position(Vec3::new(position.x, height, position.y));
324
325 if let Some(intensity) = self.properties.get("intensity") {
326 light.properties.set("intensity", intensity.clone());
327 }
328 if let Some(color) = self.properties.get("color") {
329 light.properties.set("color", color.clone());
330 }
331 if let Some(start_distance) = self.properties.get("start_distance") {
332 light
333 .properties
334 .set("start_distance", start_distance.clone());
335 }
336 if let Some(end_distance) = self.properties.get("end_distance") {
337 light.properties.set("end_distance", end_distance.clone());
338 }
339
340 light
341 }
342 }
343 }
344
345 pub fn from_sector(&self, center: Vec3<f32>, size: Vec2<f32>) -> Self {
347 let normal = Vec3::new(0.0, 1.0, 0.0);
348 let offset = 0.1; let position = center + normal * offset;
350
351 match self.light_type {
352 LightType::Point => {
353 let mut light = Light::new(LightType::Point);
354 light.set_position(position);
355
356 if let Some(start_distance) = self.properties.get("start_distance") {
358 light
359 .properties
360 .set("start_distance", start_distance.clone());
361 }
362 if let Some(end_distance) = self.properties.get("end_distance") {
363 light.properties.set("end_distance", end_distance.clone());
364 }
365 if let Some(intensity) = self.properties.get("intensity") {
366 light.properties.set("intensity", intensity.clone());
367 }
368 if let Some(color) = self.properties.get("color") {
369 light.properties.set("color", color.clone());
370 }
371
372 light
373 }
374 LightType::Ambient | LightType::AmbientDaylight => self.clone(),
375 LightType::Spot => {
376 let mut light = Light::new(LightType::Spot);
377 light.set_position(position);
378
379 light
380 .properties
381 .set("direction", Value::Vec3([0.0, 1.0, 0.0]));
382
383 if let Some(cone_angle) = self.properties.get("cone_angle") {
384 light.properties.set("cone_angle", cone_angle.clone());
385 }
386 if let Some(start_distance) = self.properties.get("start_distance") {
387 light
388 .properties
389 .set("start_distance", start_distance.clone());
390 }
391 if let Some(color) = self.properties.get("color") {
392 light.properties.set("color", color.clone());
393 }
394 if let Some(end_distance) = self.properties.get("end_distance") {
395 light.properties.set("end_distance", end_distance.clone());
396 }
397 if let Some(intensity) = self.properties.get("intensity") {
398 light.properties.set("intensity", intensity.clone());
399 }
400
401 light
402 }
403 LightType::Area => {
404 let mut light = Light::new(LightType::Area);
405 light.properties.set("from_sector", Value::Bool(true));
406 light.set_position(position);
407
408 light
409 .properties
410 .set("normal", Value::Vec3([normal.x, normal.y, normal.z]));
411
412 light.properties.set("width", Value::Float(size.x));
413 light.properties.set("height", Value::Float(size.y));
414
415 if let Some(color) = self.properties.get("color") {
416 light.properties.set("color", color.clone());
417 }
418 if let Some(start_distance) = self.properties.get("start_distance") {
419 light
420 .properties
421 .set("start_distance", start_distance.clone());
422 }
423 if let Some(end_distance) = self.properties.get("end_distance") {
424 light.properties.set("end_distance", end_distance.clone());
425 }
426 if let Some(intensity) = self.properties.get("intensity") {
427 light.properties.set("intensity", intensity.clone());
428 }
429
430 light
431 }
432 LightType::Daylight => {
433 let mut light = Light::new(LightType::Area);
434 light.set_position(position);
435
436 if let Some(intensity) = self.properties.get("intensity") {
437 light.properties.set("intensity", intensity.clone());
438 }
439 if let Some(color) = self.properties.get("color") {
440 light.properties.set("color", color.clone());
441 }
442 if let Some(end_distance) = self.properties.get("end_distance") {
443 light.properties.set("end_distance", end_distance.clone());
444 }
445 if let Some(intensity) = self.properties.get("intensity") {
446 light.properties.set("intensity", intensity.clone());
447 }
448
449 light
450 }
451 }
452 }
453}
454
455#[derive(Debug, Clone)]
457pub struct CompiledLight {
458 pub light_type: LightType,
459 pub position: Vec3<f32>,
461 pub color: [f32; 3],
462 pub intensity: f32,
463 pub emitting: bool,
464 pub start_distance: f32,
466 pub end_distance: f32,
467 pub flicker: f32,
468 pub direction: Vec3<f32>,
470 pub cone_angle: f32,
471 pub normal: Vec3<f32>,
473 pub width: f32,
474 pub height: f32,
475
476 pub from_linedef: bool,
477}
478
479impl CompiledLight {
480 pub fn position(&self) -> Vec3<f32> {
482 self.position
483 }
484
485 pub fn position_2d(&self) -> Vec2<f32> {
487 Vec2::new(self.position.x, self.position.z)
488 }
489
490 pub fn color_at(&self, point: Vec3<f32>, hash: &u32, d2: bool) -> Option<[f32; 3]> {
492 if !self.emitting {
493 return None;
494 };
495 match self.light_type {
496 LightType::Point => self.calculate_point_light(point, hash),
497 LightType::Ambient | LightType::AmbientDaylight => self.calculate_ambient_light(hash),
498 LightType::Spot => self.calculate_spot_light(point, hash),
499 LightType::Area => self.calculate_area_light(point, hash, d2),
500 LightType::Daylight => self.calculate_daylight_light(point, hash),
501 }
502 }
503
504 pub fn radiance_at(
505 &self,
506 point: Vec3<f32>,
507 surface_normal: Option<Vec3<f32>>,
508 hash: u32,
509 ) -> Option<Vec3<f32>> {
510 let incoming = match self.color_at(point, &hash, false) {
511 Some(c) => Vec3::new(c[0], c[1], c[2]),
512 None => return None,
513 };
514
515 if matches!(
517 self.light_type,
518 LightType::Ambient | LightType::AmbientDaylight | LightType::Daylight
519 ) {
520 return Some(incoming);
521 }
522
523 let n = match surface_normal {
525 Some(n) => n,
526 None => return Some(incoming),
527 };
528
529 let dir_to_light = (self.position - point).normalized();
531 let lambert = n.dot(dir_to_light).max(0.0);
532 Some(incoming * lambert)
533 }
534
535 fn calculate_point_light(&self, point: Vec3<f32>, hash: &u32) -> Option<[f32; 3]> {
536 let distance = (point - self.position).magnitude();
537
538 if distance >= self.end_distance {
540 return None;
541 }
542
543 if distance <= self.start_distance {
545 return Some(self.apply_flicker(self.color, self.intensity, self.flicker, hash));
546 }
547
548 let attenuation = self.smoothstep(self.end_distance, self.start_distance, distance);
550 let adjusted_intensity = self.intensity * attenuation;
551 Some(self.apply_flicker(self.color, adjusted_intensity, self.flicker, hash))
552 }
553
554 fn calculate_ambient_light(&self, hash: &u32) -> Option<[f32; 3]> {
555 Some(self.apply_flicker(self.color, self.intensity, self.flicker, hash))
557 }
558
559 fn calculate_spot_light(&self, point: Vec3<f32>, hash: &u32) -> Option<[f32; 3]> {
560 let distance = (point - self.position).magnitude();
561 if distance >= self.end_distance {
562 return None;
563 }
564
565 let attenuation = if distance <= self.start_distance {
566 1.0
567 } else {
568 1.0 - ((distance - self.start_distance) / (self.end_distance - self.start_distance))
569 };
570
571 let direction_to_point = (point - self.position).normalized();
573 let angle = self.direction.dot(direction_to_point).acos();
574 if angle > self.cone_angle {
575 return None;
576 }
577
578 let adjusted_intensity = self.intensity * attenuation;
579 Some(self.apply_flicker(self.color, adjusted_intensity, self.flicker, hash))
580 }
581
582 fn calculate_area_light(&self, point: Vec3<f32>, _hash: &u32, d2: bool) -> Option<[f32; 3]> {
583 let to_point = point - self.position;
584 let distance = to_point.magnitude();
585
586 if distance >= self.end_distance {
587 return None;
588 }
589
590 if distance < 0.1 {
591 return Some(self.color);
592 }
593
594 let distance_attenuation = if distance <= self.start_distance {
595 1.0
596 } else {
597 self.smoothstep(self.end_distance, self.start_distance, distance)
598 };
599 let area = self.width * self.height;
600
601 let direction = to_point.normalized();
602
603 if self.from_linedef {
604 let attenuation = distance_attenuation * area * self.intensity;
606 Some([
607 self.color[0] * attenuation,
608 self.color[1] * attenuation,
609 self.color[2] * attenuation,
610 ])
611 } else {
612 let attenuation = if d2 {
613 let distance_x = (to_point.x / (self.width * 0.5)).abs(); let distance_y = (to_point.y / (self.height * 0.5)).abs(); let attenuation_x = (1.0 - distance_x).max(0.0);
616 let attenuation_y = (1.0 - distance_y).max(0.0);
617 attenuation_x * attenuation_y * distance_attenuation * self.intensity
618 } else {
619 let angle_attenuation = self.normal.dot(direction).max(0.0);
620 angle_attenuation * distance_attenuation * area * self.intensity
621 };
622 Some([
623 self.color[0] * attenuation,
624 self.color[1] * attenuation,
625 self.color[2] * attenuation,
626 ])
627 }
628 }
629
630 fn calculate_daylight_light(&self, point: Vec3<f32>, _hash: &u32) -> Option<[f32; 3]> {
631 let to_point = point - self.position;
632 let distance = to_point.magnitude();
633
634 if distance >= self.end_distance {
636 return None;
637 }
638
639 let direction = to_point.normalized();
640 let angle_attenuation = self.normal.dot(direction).max(0.0);
641 let distance_attenuation = if distance <= self.start_distance {
642 1.0
643 } else {
644 self.smoothstep(self.end_distance, self.start_distance, distance)
645 };
646 let attenuation = angle_attenuation * distance_attenuation * self.intensity;
647
648 Some([
649 self.color[0] * attenuation,
650 self.color[1] * attenuation,
651 self.color[2] * attenuation,
652 ])
653 }
654
655 fn apply_flicker(&self, color: [f32; 3], intensity: f32, flicker: f32, hash: &u32) -> [f32; 3] {
657 let flicker_factor = if flicker > 0.0 {
658 let combined_hash = hash.wrapping_add(
659 (self.position.x as u32 + self.position.y as u32 + self.position.z as u32) * 100,
660 );
661 let flicker_value = (combined_hash as f32 / u32::MAX as f32).clamp(0.0, 1.0);
662 1.0 - flicker_value * flicker
663 } else {
664 1.0
665 };
666
667 [
668 color[0] * intensity * flicker_factor,
669 color[1] * intensity * flicker_factor,
670 color[2] * intensity * flicker_factor,
671 ]
672 }
673
674 fn smoothstep(&self, edge0: f32, edge1: f32, x: f32) -> f32 {
675 let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
676 t * t * (3.0 - 2.0 * t)
677 }
678}