1use super::lights::{Vec3, Mat4, Color, LightId, Light, CascadeShadowParams};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
15pub struct ShadowMap {
16 pub width: u32,
18 pub height: u32,
20 pub depth_buffer: Vec<f32>,
22 pub view_projection: Mat4,
24 pub near: f32,
26 pub far: f32,
28}
29
30impl ShadowMap {
31 pub fn new(width: u32, height: u32) -> Self {
33 let size = (width as usize) * (height as usize);
34 Self {
35 width,
36 height,
37 depth_buffer: vec![1.0; size],
38 view_projection: Mat4::IDENTITY,
39 near: 0.1,
40 far: 100.0,
41 }
42 }
43
44 pub fn clear(&mut self) {
46 for d in self.depth_buffer.iter_mut() {
47 *d = 1.0;
48 }
49 }
50
51 pub fn write_depth(&mut self, x: u32, y: u32, depth: f32) {
53 if x < self.width && y < self.height {
54 let idx = (y as usize) * (self.width as usize) + (x as usize);
55 if depth < self.depth_buffer[idx] {
56 self.depth_buffer[idx] = depth;
57 }
58 }
59 }
60
61 pub fn read_depth(&self, x: u32, y: u32) -> f32 {
63 if x < self.width && y < self.height {
64 self.depth_buffer[(y as usize) * (self.width as usize) + (x as usize)]
65 } else {
66 1.0
67 }
68 }
69
70 pub fn sample_bilinear(&self, u: f32, v: f32) -> f32 {
72 let u = u.clamp(0.0, 1.0);
73 let v = v.clamp(0.0, 1.0);
74
75 let fx = u * (self.width as f32 - 1.0);
76 let fy = v * (self.height as f32 - 1.0);
77
78 let x0 = fx.floor() as u32;
79 let y0 = fy.floor() as u32;
80 let x1 = (x0 + 1).min(self.width - 1);
81 let y1 = (y0 + 1).min(self.height - 1);
82
83 let frac_x = fx - fx.floor();
84 let frac_y = fy - fy.floor();
85
86 let d00 = self.read_depth(x0, y0);
87 let d10 = self.read_depth(x1, y0);
88 let d01 = self.read_depth(x0, y1);
89 let d11 = self.read_depth(x1, y1);
90
91 let top = d00 + (d10 - d00) * frac_x;
92 let bottom = d01 + (d11 - d01) * frac_x;
93 top + (bottom - top) * frac_y
94 }
95
96 pub fn project_point(&self, world_pos: Vec3) -> (f32, f32, f32) {
98 let clip = self.view_projection.transform_point(world_pos);
99 let u = clip.x * 0.5 + 0.5;
100 let v = clip.y * 0.5 + 0.5;
101 let depth = clip.z * 0.5 + 0.5;
102 (u, v, depth)
103 }
104
105 pub fn is_in_shadow(&self, world_pos: Vec3, bias: f32) -> bool {
107 let (u, v, depth) = self.project_point(world_pos);
108 if u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 {
109 return false; }
111 let stored_depth = self.sample_bilinear(u, v);
112 depth - bias > stored_depth
113 }
114
115 pub fn rasterize_triangle(&mut self, v0: Vec3, v1: Vec3, v2: Vec3) {
117 let p0 = self.view_projection.transform_point(v0);
118 let p1 = self.view_projection.transform_point(v1);
119 let p2 = self.view_projection.transform_point(v2);
120
121 let sx0 = (p0.x * 0.5 + 0.5) * self.width as f32;
123 let sy0 = (p0.y * 0.5 + 0.5) * self.height as f32;
124 let sz0 = p0.z * 0.5 + 0.5;
125
126 let sx1 = (p1.x * 0.5 + 0.5) * self.width as f32;
127 let sy1 = (p1.y * 0.5 + 0.5) * self.height as f32;
128 let sz1 = p1.z * 0.5 + 0.5;
129
130 let sx2 = (p2.x * 0.5 + 0.5) * self.width as f32;
131 let sy2 = (p2.y * 0.5 + 0.5) * self.height as f32;
132 let sz2 = p2.z * 0.5 + 0.5;
133
134 let min_x = sx0.min(sx1).min(sx2).max(0.0) as u32;
136 let max_x = sx0.max(sx1).max(sx2).min(self.width as f32 - 1.0) as u32;
137 let min_y = sy0.min(sy1).min(sy2).max(0.0) as u32;
138 let max_y = sy0.max(sy1).max(sy2).min(self.height as f32 - 1.0) as u32;
139
140 for y in min_y..=max_y {
142 for x in min_x..=max_x {
143 let px = x as f32 + 0.5;
144 let py = y as f32 + 0.5;
145
146 let area = edge_function(sx0, sy0, sx1, sy1, sx2, sy2);
147 if area.abs() < 1e-10 {
148 continue;
149 }
150
151 let w0 = edge_function(sx1, sy1, sx2, sy2, px, py);
152 let w1 = edge_function(sx2, sy2, sx0, sy0, px, py);
153 let w2 = edge_function(sx0, sy0, sx1, sy1, px, py);
154
155 if (w0 >= 0.0 && w1 >= 0.0 && w2 >= 0.0) || (w0 <= 0.0 && w1 <= 0.0 && w2 <= 0.0) {
156 let inv_area = 1.0 / area;
157 let b0 = w0 * inv_area;
158 let b1 = w1 * inv_area;
159 let b2 = w2 * inv_area;
160
161 let depth = sz0 * b0 + sz1 * b1 + sz2 * b2;
162 self.write_depth(x, y, depth.clamp(0.0, 1.0));
163 }
164 }
165 }
166 }
167
168 pub fn texel_count(&self) -> usize {
170 (self.width as usize) * (self.height as usize)
171 }
172
173 pub fn memory_bytes(&self) -> usize {
175 self.depth_buffer.len() * 4
176 }
177}
178
179fn edge_function(ax: f32, ay: f32, bx: f32, by: f32, cx: f32, cy: f32) -> f32 {
181 (cx - ax) * (by - ay) - (cy - ay) * (bx - ax)
182}
183
184#[derive(Debug, Clone)]
189pub struct CascadedShadowMap {
190 pub cascades: [ShadowMap; 4],
192 pub cascade_count: u32,
194 pub cascade_vp: [Mat4; 4],
196 pub split_distances: [f32; 5],
198 pub resolution: u32,
200 pub blend_cascades: bool,
202 pub blend_band: f32,
204}
205
206impl CascadedShadowMap {
207 pub fn new(resolution: u32, cascade_count: u32) -> Self {
208 let count = cascade_count.clamp(1, 4);
209 Self {
210 cascades: [
211 ShadowMap::new(resolution, resolution),
212 ShadowMap::new(resolution, resolution),
213 ShadowMap::new(resolution, resolution),
214 ShadowMap::new(resolution, resolution),
215 ],
216 cascade_count: count,
217 cascade_vp: [Mat4::IDENTITY; 4],
218 split_distances: [0.1, 10.0, 30.0, 80.0, 200.0],
219 resolution,
220 blend_cascades: true,
221 blend_band: 0.1,
222 }
223 }
224
225 pub fn update_splits(&mut self, params: &CascadeShadowParams) {
227 self.cascade_count = params.cascade_count.min(4);
228 self.split_distances = params.split_distances;
229 self.blend_band = params.blend_band;
230 }
231
232 pub fn set_cascade_vp(&mut self, cascade: usize, vp: Mat4) {
234 if cascade < 4 {
235 self.cascade_vp[cascade] = vp;
236 self.cascades[cascade].view_projection = vp;
237 }
238 }
239
240 pub fn compute_cascade_matrices(
242 &mut self,
243 light_dir: Vec3,
244 camera_frustum_slices: &[[Vec3; 8]; 4],
245 params: &CascadeShadowParams,
246 ) {
247 self.update_splits(params);
248 let count = self.cascade_count as usize;
249 for i in 0..count {
250 let vp = params.cascade_view_projection(light_dir, &camera_frustum_slices[i]);
251 self.set_cascade_vp(i, vp);
252 }
253 }
254
255 pub fn clear_all(&mut self) {
257 for i in 0..self.cascade_count as usize {
258 self.cascades[i].clear();
259 }
260 }
261
262 pub fn select_cascade(&self, view_depth: f32) -> usize {
264 for i in 0..self.cascade_count as usize {
265 if view_depth < self.split_distances[i + 1] {
266 return i;
267 }
268 }
269 (self.cascade_count as usize).saturating_sub(1)
270 }
271
272 pub fn is_in_shadow(&self, world_pos: Vec3, view_depth: f32, bias: &ShadowBias) -> bool {
274 let cascade = self.select_cascade(view_depth);
275 let effective_bias = bias.compute(0.0, 0.0); self.cascades[cascade].is_in_shadow(world_pos, effective_bias)
277 }
278
279 pub fn shadow_factor(
281 &self,
282 world_pos: Vec3,
283 view_depth: f32,
284 bias: &ShadowBias,
285 ) -> f32 {
286 let cascade = self.select_cascade(view_depth);
287 let effective_bias = bias.compute(0.0, 0.0);
288 let in_shadow = self.cascades[cascade].is_in_shadow(world_pos, effective_bias);
289
290 let base_factor = if in_shadow { 0.0 } else { 1.0 };
291
292 if !self.blend_cascades || cascade + 1 >= self.cascade_count as usize {
293 return base_factor;
294 }
295
296 let split_near = self.split_distances[cascade + 1];
298 let blend_start = split_near * (1.0 - self.blend_band);
299 if view_depth > blend_start {
300 let blend_t = (view_depth - blend_start) / (split_near - blend_start);
301 let next_in_shadow = self.cascades[cascade + 1].is_in_shadow(world_pos, effective_bias);
302 let next_factor = if next_in_shadow { 0.0 } else { 1.0 };
303 base_factor * (1.0 - blend_t) + next_factor * blend_t
304 } else {
305 base_factor
306 }
307 }
308
309 pub fn compute_frustum_slice(
311 near: f32,
312 far: f32,
313 fov_y: f32,
314 aspect: f32,
315 camera_pos: Vec3,
316 camera_forward: Vec3,
317 camera_up: Vec3,
318 ) -> [Vec3; 8] {
319 let camera_right = camera_forward.cross(camera_up).normalize();
320 let corrected_up = camera_right.cross(camera_forward).normalize();
321
322 let near_h = (fov_y * 0.5).tan() * near;
323 let near_w = near_h * aspect;
324 let far_h = (fov_y * 0.5).tan() * far;
325 let far_w = far_h * aspect;
326
327 let near_center = camera_pos + camera_forward * near;
328 let far_center = camera_pos + camera_forward * far;
329
330 [
331 near_center + corrected_up * near_h - camera_right * near_w,
332 near_center + corrected_up * near_h + camera_right * near_w,
333 near_center - corrected_up * near_h + camera_right * near_w,
334 near_center - corrected_up * near_h - camera_right * near_w,
335 far_center + corrected_up * far_h - camera_right * far_w,
336 far_center + corrected_up * far_h + camera_right * far_w,
337 far_center - corrected_up * far_h + camera_right * far_w,
338 far_center - corrected_up * far_h - camera_right * far_w,
339 ]
340 }
341
342 pub fn memory_bytes(&self) -> usize {
344 let count = self.cascade_count as usize;
345 (0..count).map(|i| self.cascades[i].memory_bytes()).sum()
346 }
347}
348
349#[derive(Debug, Clone)]
353pub struct OmniShadowMap {
354 pub faces: [ShadowMap; 6],
356 pub light_position: Vec3,
358 pub radius: f32,
360 pub resolution: u32,
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub enum CubeFace {
367 PositiveX = 0,
368 NegativeX = 1,
369 PositiveY = 2,
370 NegativeY = 3,
371 PositiveZ = 4,
372 NegativeZ = 5,
373}
374
375impl CubeFace {
376 pub const ALL: [CubeFace; 6] = [
377 CubeFace::PositiveX,
378 CubeFace::NegativeX,
379 CubeFace::PositiveY,
380 CubeFace::NegativeY,
381 CubeFace::PositiveZ,
382 CubeFace::NegativeZ,
383 ];
384
385 pub fn directions(self) -> (Vec3, Vec3) {
387 match self {
388 CubeFace::PositiveX => (Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
389 CubeFace::NegativeX => (Vec3::new(-1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
390 CubeFace::PositiveY => (Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0)),
391 CubeFace::NegativeY => (Vec3::new(0.0, -1.0, 0.0), Vec3::new(0.0, 0.0, -1.0)),
392 CubeFace::PositiveZ => (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.0, -1.0, 0.0)),
393 CubeFace::NegativeZ => (Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, -1.0, 0.0)),
394 }
395 }
396}
397
398impl OmniShadowMap {
399 pub fn new(resolution: u32, light_position: Vec3, radius: f32) -> Self {
400 let mut osm = Self {
401 faces: [
402 ShadowMap::new(resolution, resolution),
403 ShadowMap::new(resolution, resolution),
404 ShadowMap::new(resolution, resolution),
405 ShadowMap::new(resolution, resolution),
406 ShadowMap::new(resolution, resolution),
407 ShadowMap::new(resolution, resolution),
408 ],
409 light_position,
410 radius,
411 resolution,
412 };
413 osm.update_matrices();
414 osm
415 }
416
417 pub fn update_matrices(&mut self) {
419 let proj = Mat4::perspective(std::f32::consts::FRAC_PI_2, 1.0, 0.1, self.radius);
420 for face in CubeFace::ALL {
421 let (forward, up) = face.directions();
422 let target = self.light_position + forward;
423 let view = Mat4::look_at(self.light_position, target, up);
424 let vp = proj.mul_mat4(view);
425 self.faces[face as usize].view_projection = vp;
426 self.faces[face as usize].near = 0.1;
427 self.faces[face as usize].far = self.radius;
428 }
429 }
430
431 pub fn set_position(&mut self, pos: Vec3) {
433 self.light_position = pos;
434 self.update_matrices();
435 }
436
437 pub fn clear_all(&mut self) {
439 for face in &mut self.faces {
440 face.clear();
441 }
442 }
443
444 pub fn select_face(direction: Vec3) -> CubeFace {
446 let abs = direction.abs();
447 if abs.x >= abs.y && abs.x >= abs.z {
448 if direction.x >= 0.0 { CubeFace::PositiveX } else { CubeFace::NegativeX }
449 } else if abs.y >= abs.x && abs.y >= abs.z {
450 if direction.y >= 0.0 { CubeFace::PositiveY } else { CubeFace::NegativeY }
451 } else if direction.z >= 0.0 {
452 CubeFace::PositiveZ
453 } else {
454 CubeFace::NegativeZ
455 }
456 }
457
458 pub fn is_in_shadow(&self, world_pos: Vec3, bias: f32) -> bool {
460 let dir = world_pos - self.light_position;
461 let dist = dir.length();
462 if dist > self.radius {
463 return false;
464 }
465 let face = Self::select_face(dir);
466 self.faces[face as usize].is_in_shadow(world_pos, bias)
467 }
468
469 pub fn shadow_factor_pcf(&self, world_pos: Vec3, bias: f32, kernel: &PcfKernel) -> f32 {
471 let dir = world_pos - self.light_position;
472 let dist = dir.length();
473 if dist > self.radius {
474 return 1.0;
475 }
476 let face = Self::select_face(dir);
477 let shadow_map = &self.faces[face as usize];
478 kernel.sample(shadow_map, world_pos, bias)
479 }
480
481 pub fn memory_bytes(&self) -> usize {
483 self.faces.iter().map(|f| f.memory_bytes()).sum()
484 }
485}
486
487#[derive(Debug, Clone, Copy)]
491pub struct ShadowAtlasRegion {
492 pub x: u32,
494 pub y: u32,
496 pub width: u32,
498 pub height: u32,
500 pub light_id: Option<LightId>,
502}
503
504impl ShadowAtlasRegion {
505 pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
506 Self {
507 x,
508 y,
509 width,
510 height,
511 light_id: None,
512 }
513 }
514
515 pub fn atlas_to_region_uv(&self, atlas_width: u32, atlas_height: u32, u: f32, v: f32) -> (f32, f32) {
517 let region_u = (u * atlas_width as f32 - self.x as f32) / self.width as f32;
518 let region_v = (v * atlas_height as f32 - self.y as f32) / self.height as f32;
519 (region_u, region_v)
520 }
521
522 pub fn region_to_atlas_uv(&self, atlas_width: u32, atlas_height: u32, u: f32, v: f32) -> (f32, f32) {
524 let atlas_u = (self.x as f32 + u * self.width as f32) / atlas_width as f32;
525 let atlas_v = (self.y as f32 + v * self.height as f32) / atlas_height as f32;
526 (atlas_u, atlas_v)
527 }
528
529 pub fn contains(&self, px: u32, py: u32) -> bool {
531 px >= self.x && px < self.x + self.width && py >= self.y && py < self.y + self.height
532 }
533
534 pub fn area(&self) -> u32 {
536 self.width * self.height
537 }
538}
539
540#[derive(Debug, Clone)]
542pub struct ShadowAtlas {
543 pub width: u32,
545 pub height: u32,
547 pub depth_buffer: Vec<f32>,
549 pub regions: Vec<ShadowAtlasRegion>,
551 free_shelves: Vec<AtlasShelf>,
553 current_shelf_y: u32,
555 current_shelf_height: u32,
557 current_shelf_x: u32,
559}
560
561#[derive(Debug, Clone)]
562struct AtlasShelf {
563 y: u32,
564 height: u32,
565 remaining_width: u32,
566 x_offset: u32,
567}
568
569impl ShadowAtlas {
570 pub fn new(width: u32, height: u32) -> Self {
571 let size = (width as usize) * (height as usize);
572 Self {
573 width,
574 height,
575 depth_buffer: vec![1.0; size],
576 regions: Vec::new(),
577 free_shelves: Vec::new(),
578 current_shelf_y: 0,
579 current_shelf_height: 0,
580 current_shelf_x: 0,
581 }
582 }
583
584 pub fn clear(&mut self) {
586 for d in self.depth_buffer.iter_mut() {
587 *d = 1.0;
588 }
589 }
590
591 pub fn reset_allocations(&mut self) {
593 self.regions.clear();
594 self.free_shelves.clear();
595 self.current_shelf_y = 0;
596 self.current_shelf_height = 0;
597 self.current_shelf_x = 0;
598 }
599
600 pub fn allocate(&mut self, width: u32, height: u32, light_id: LightId) -> Option<usize> {
602 for shelf in &mut self.free_shelves {
604 if height <= shelf.height && width <= shelf.remaining_width {
605 let region = ShadowAtlasRegion {
606 x: shelf.x_offset,
607 y: shelf.y,
608 width,
609 height,
610 light_id: Some(light_id),
611 };
612 shelf.x_offset += width;
613 shelf.remaining_width -= width;
614 let idx = self.regions.len();
615 self.regions.push(region);
616 return Some(idx);
617 }
618 }
619
620 if self.current_shelf_x + width <= self.width && height <= self.current_shelf_height {
622 let region = ShadowAtlasRegion {
623 x: self.current_shelf_x,
624 y: self.current_shelf_y,
625 width,
626 height,
627 light_id: Some(light_id),
628 };
629 self.current_shelf_x += width;
630 let idx = self.regions.len();
631 self.regions.push(region);
632 return Some(idx);
633 }
634
635 if self.current_shelf_height > 0 {
637 let remaining = self.width - self.current_shelf_x;
639 if remaining > 0 {
640 self.free_shelves.push(AtlasShelf {
641 y: self.current_shelf_y,
642 height: self.current_shelf_height,
643 remaining_width: remaining,
644 x_offset: self.current_shelf_x,
645 });
646 }
647 }
648
649 let new_y = self.current_shelf_y + self.current_shelf_height;
650 if new_y + height > self.height || width > self.width {
651 return None; }
653
654 self.current_shelf_y = new_y;
655 self.current_shelf_height = height;
656 self.current_shelf_x = width;
657
658 let region = ShadowAtlasRegion {
659 x: 0,
660 y: new_y,
661 width,
662 height,
663 light_id: Some(light_id),
664 };
665 let idx = self.regions.len();
666 self.regions.push(region);
667 Some(idx)
668 }
669
670 pub fn write_depth_in_region(&mut self, region_idx: usize, local_x: u32, local_y: u32, depth: f32) {
672 if let Some(region) = self.regions.get(region_idx) {
673 let ax = region.x + local_x;
674 let ay = region.y + local_y;
675 if ax < self.width && ay < self.height {
676 let idx = (ay as usize) * (self.width as usize) + (ax as usize);
677 if depth < self.depth_buffer[idx] {
678 self.depth_buffer[idx] = depth;
679 }
680 }
681 }
682 }
683
684 pub fn read_depth_in_region(&self, region_idx: usize, local_x: u32, local_y: u32) -> f32 {
686 if let Some(region) = self.regions.get(region_idx) {
687 let ax = region.x + local_x;
688 let ay = region.y + local_y;
689 if ax < self.width && ay < self.height {
690 return self.depth_buffer[(ay as usize) * (self.width as usize) + (ax as usize)];
691 }
692 }
693 1.0
694 }
695
696 pub fn sample_region_bilinear(&self, region_idx: usize, u: f32, v: f32) -> f32 {
698 let region = match self.regions.get(region_idx) {
699 Some(r) => r,
700 None => return 1.0,
701 };
702
703 let u = u.clamp(0.0, 1.0);
704 let v = v.clamp(0.0, 1.0);
705
706 let fx = u * (region.width as f32 - 1.0);
707 let fy = v * (region.height as f32 - 1.0);
708
709 let x0 = fx.floor() as u32;
710 let y0 = fy.floor() as u32;
711 let x1 = (x0 + 1).min(region.width - 1);
712 let y1 = (y0 + 1).min(region.height - 1);
713
714 let frac_x = fx - fx.floor();
715 let frac_y = fy - fy.floor();
716
717 let d00 = self.read_depth_in_region(region_idx, x0, y0);
718 let d10 = self.read_depth_in_region(region_idx, x1, y0);
719 let d01 = self.read_depth_in_region(region_idx, x0, y1);
720 let d11 = self.read_depth_in_region(region_idx, x1, y1);
721
722 let top = d00 + (d10 - d00) * frac_x;
723 let bottom = d01 + (d11 - d01) * frac_x;
724 top + (bottom - top) * frac_y
725 }
726
727 pub fn region_count(&self) -> usize {
729 self.regions.len()
730 }
731
732 pub fn memory_bytes(&self) -> usize {
734 self.depth_buffer.len() * 4
735 }
736
737 pub fn utilization(&self) -> f32 {
739 let total = (self.width as u64) * (self.height as u64);
740 if total == 0 {
741 return 0.0;
742 }
743 let used: u64 = self.regions.iter().map(|r| r.area() as u64).sum();
744 used as f32 / total as f32
745 }
746}
747
748#[derive(Debug, Clone)]
752pub struct PcfKernel {
753 pub offsets: Vec<(f32, f32)>,
755 pub weights: Vec<f32>,
757 pub texel_size: f32,
759}
760
761impl PcfKernel {
762 pub fn kernel_3x3(texel_size: f32) -> Self {
764 let mut offsets = Vec::with_capacity(9);
765 let mut weights = Vec::with_capacity(9);
766 for dy in -1..=1 {
767 for dx in -1..=1 {
768 offsets.push((dx as f32, dy as f32));
769 let dist_sq = (dx * dx + dy * dy) as f32;
771 let w = (-dist_sq * 0.5).exp();
772 weights.push(w);
773 }
774 }
775 let sum: f32 = weights.iter().sum();
776 for w in weights.iter_mut() {
777 *w /= sum;
778 }
779 Self { offsets, weights, texel_size }
780 }
781
782 pub fn kernel_5x5(texel_size: f32) -> Self {
784 let mut offsets = Vec::with_capacity(25);
785 let mut weights = Vec::with_capacity(25);
786 for dy in -2..=2 {
787 for dx in -2..=2 {
788 offsets.push((dx as f32, dy as f32));
789 let dist_sq = (dx * dx + dy * dy) as f32;
790 let w = (-dist_sq * 0.25).exp();
791 weights.push(w);
792 }
793 }
794 let sum: f32 = weights.iter().sum();
795 for w in weights.iter_mut() {
796 *w /= sum;
797 }
798 Self { offsets, weights, texel_size }
799 }
800
801 pub fn poisson_disk(sample_count: usize, texel_size: f32) -> Self {
803 let mut offsets = Vec::with_capacity(sample_count);
805 let mut weights = Vec::with_capacity(sample_count);
806
807 let golden_angle = std::f32::consts::PI * (3.0 - 5.0_f32.sqrt());
808 for i in 0..sample_count {
809 let r = ((i as f32 + 0.5) / sample_count as f32).sqrt() * 2.0;
810 let theta = i as f32 * golden_angle;
811 offsets.push((r * theta.cos(), r * theta.sin()));
812 weights.push(1.0 / sample_count as f32);
813 }
814
815 Self { offsets, weights, texel_size }
816 }
817
818 pub fn sample(&self, shadow_map: &ShadowMap, world_pos: Vec3, bias: f32) -> f32 {
820 let (u, v, depth) = shadow_map.project_point(world_pos);
821 if u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 {
822 return 1.0; }
824
825 let mut shadow_sum = 0.0f32;
826 for (i, &(dx, dy)) in self.offsets.iter().enumerate() {
827 let su = u + dx * self.texel_size;
828 let sv = v + dy * self.texel_size;
829 let stored_depth = shadow_map.sample_bilinear(su, sv);
830 let lit = if depth - bias <= stored_depth { 1.0 } else { 0.0 };
831 shadow_sum += lit * self.weights[i];
832 }
833 shadow_sum
834 }
835
836 pub fn sample_atlas(
838 &self,
839 atlas: &ShadowAtlas,
840 region_idx: usize,
841 u: f32,
842 v: f32,
843 depth: f32,
844 bias: f32,
845 ) -> f32 {
846 let region = match atlas.regions.get(region_idx) {
847 Some(r) => r,
848 None => return 1.0,
849 };
850
851 let texel_u = self.texel_size / region.width as f32;
852 let texel_v = self.texel_size / region.height as f32;
853
854 let mut shadow_sum = 0.0f32;
855 for (i, &(dx, dy)) in self.offsets.iter().enumerate() {
856 let su = (u + dx * texel_u).clamp(0.0, 1.0);
857 let sv = (v + dy * texel_v).clamp(0.0, 1.0);
858 let stored_depth = atlas.sample_region_bilinear(region_idx, su, sv);
859 let lit = if depth - bias <= stored_depth { 1.0 } else { 0.0 };
860 shadow_sum += lit * self.weights[i];
861 }
862 shadow_sum
863 }
864}
865
866#[derive(Debug, Clone)]
871pub struct VarianceShadowMap {
872 pub width: u32,
873 pub height: u32,
874 pub moment1: Vec<f32>,
876 pub moment2: Vec<f32>,
878 pub view_projection: Mat4,
879 pub min_variance: f32,
881 pub light_bleed_reduction: f32,
883}
884
885impl VarianceShadowMap {
886 pub fn new(width: u32, height: u32) -> Self {
887 let size = (width as usize) * (height as usize);
888 Self {
889 width,
890 height,
891 moment1: vec![1.0; size],
892 moment2: vec![1.0; size],
893 view_projection: Mat4::IDENTITY,
894 min_variance: 0.00002,
895 light_bleed_reduction: 0.2,
896 }
897 }
898
899 pub fn clear(&mut self) {
901 for v in self.moment1.iter_mut() {
902 *v = 1.0;
903 }
904 for v in self.moment2.iter_mut() {
905 *v = 1.0;
906 }
907 }
908
909 pub fn write_depth(&mut self, x: u32, y: u32, depth: f32) {
911 if x < self.width && y < self.height {
912 let idx = (y as usize) * (self.width as usize) + (x as usize);
913 if depth < self.moment1[idx] {
916 self.moment1[idx] = depth;
917 self.moment2[idx] = depth * depth;
918 }
919 }
920 }
921
922 pub fn sample_moments(&self, u: f32, v: f32) -> (f32, f32) {
924 let u = u.clamp(0.0, 1.0);
925 let v = v.clamp(0.0, 1.0);
926
927 let fx = u * (self.width as f32 - 1.0);
928 let fy = v * (self.height as f32 - 1.0);
929
930 let x0 = fx.floor() as u32;
931 let y0 = fy.floor() as u32;
932 let x1 = (x0 + 1).min(self.width - 1);
933 let y1 = (y0 + 1).min(self.height - 1);
934
935 let frac_x = fx - fx.floor();
936 let frac_y = fy - fy.floor();
937
938 let read = |buf: &[f32], x: u32, y: u32| -> f32 {
939 buf[(y as usize) * (self.width as usize) + (x as usize)]
940 };
941
942 let m1_00 = read(&self.moment1, x0, y0);
943 let m1_10 = read(&self.moment1, x1, y0);
944 let m1_01 = read(&self.moment1, x0, y1);
945 let m1_11 = read(&self.moment1, x1, y1);
946
947 let m2_00 = read(&self.moment2, x0, y0);
948 let m2_10 = read(&self.moment2, x1, y0);
949 let m2_01 = read(&self.moment2, x0, y1);
950 let m2_11 = read(&self.moment2, x1, y1);
951
952 let m1_top = m1_00 + (m1_10 - m1_00) * frac_x;
953 let m1_bot = m1_01 + (m1_11 - m1_01) * frac_x;
954 let m1 = m1_top + (m1_bot - m1_top) * frac_y;
955
956 let m2_top = m2_00 + (m2_10 - m2_00) * frac_x;
957 let m2_bot = m2_01 + (m2_11 - m2_01) * frac_x;
958 let m2 = m2_top + (m2_bot - m2_top) * frac_y;
959
960 (m1, m2)
961 }
962
963 pub fn shadow_factor(&self, world_pos: Vec3) -> f32 {
965 let clip = self.view_projection.transform_point(world_pos);
966 let u = clip.x * 0.5 + 0.5;
967 let v = clip.y * 0.5 + 0.5;
968 let depth = clip.z * 0.5 + 0.5;
969
970 if u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0 {
971 return 1.0;
972 }
973
974 let (mean, mean_sq) = self.sample_moments(u, v);
975
976 if depth <= mean {
978 return 1.0;
979 }
980
981 let variance = (mean_sq - mean * mean).max(self.min_variance);
983 let d = depth - mean;
984 let p_max = variance / (variance + d * d);
985
986 let reduced = ((p_max - self.light_bleed_reduction) / (1.0 - self.light_bleed_reduction)).max(0.0);
988 reduced
989 }
990
991 pub fn blur(&mut self, radius: u32) {
993 let w = self.width as usize;
994 let h = self.height as usize;
995
996 let mut temp1 = vec![0.0f32; w * h];
998 let mut temp2 = vec![0.0f32; w * h];
999
1000 for y in 0..h {
1001 for x in 0..w {
1002 let mut sum1 = 0.0f32;
1003 let mut sum2 = 0.0f32;
1004 let mut count = 0.0f32;
1005
1006 let x_start = x.saturating_sub(radius as usize);
1007 let x_end = (x + radius as usize + 1).min(w);
1008
1009 for sx in x_start..x_end {
1010 sum1 += self.moment1[y * w + sx];
1011 sum2 += self.moment2[y * w + sx];
1012 count += 1.0;
1013 }
1014 temp1[y * w + x] = sum1 / count;
1015 temp2[y * w + x] = sum2 / count;
1016 }
1017 }
1018
1019 for y in 0..h {
1021 for x in 0..w {
1022 let mut sum1 = 0.0f32;
1023 let mut sum2 = 0.0f32;
1024 let mut count = 0.0f32;
1025
1026 let y_start = y.saturating_sub(radius as usize);
1027 let y_end = (y + radius as usize + 1).min(h);
1028
1029 for sy in y_start..y_end {
1030 sum1 += temp1[sy * w + x];
1031 sum2 += temp2[sy * w + x];
1032 count += 1.0;
1033 }
1034 self.moment1[y * w + x] = sum1 / count;
1035 self.moment2[y * w + x] = sum2 / count;
1036 }
1037 }
1038 }
1039
1040 pub fn memory_bytes(&self) -> usize {
1042 (self.moment1.len() + self.moment2.len()) * 4
1043 }
1044}
1045
1046#[derive(Debug, Clone, Copy)]
1050pub struct ShadowBias {
1051 pub constant: f32,
1053 pub slope_scale: f32,
1055 pub normal_offset: f32,
1057}
1058
1059impl Default for ShadowBias {
1060 fn default() -> Self {
1061 Self {
1062 constant: 0.005,
1063 slope_scale: 1.5,
1064 normal_offset: 0.02,
1065 }
1066 }
1067}
1068
1069impl ShadowBias {
1070 pub fn new(constant: f32, slope_scale: f32, normal_offset: f32) -> Self {
1071 Self { constant, slope_scale, normal_offset }
1072 }
1073
1074 pub fn compute(&self, depth_slope: f32, _cos_angle: f32) -> f32 {
1076 self.constant + self.slope_scale * depth_slope
1077 }
1078
1079 pub fn normal_offset_vec(&self, normal: Vec3) -> Vec3 {
1081 normal * self.normal_offset
1082 }
1083
1084 pub fn apply_normal_offset(&self, position: Vec3, normal: Vec3) -> Vec3 {
1086 position + self.normal_offset_vec(normal)
1087 }
1088}
1089
1090#[derive(Debug, Clone)]
1094pub struct ShadowConfig {
1095 pub max_distance: f32,
1097 pub fade_start: f32,
1099 pub atlas_resolution: u32,
1101 pub default_resolution: u32,
1103 pub bias: ShadowBias,
1105 pub pcf_mode: PcfMode,
1107 pub use_vsm: bool,
1109 pub max_shadow_casters: u32,
1111 pub cull_shadow_casters: bool,
1113}
1114
1115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1117pub enum PcfMode {
1118 None,
1119 Pcf3x3,
1120 Pcf5x5,
1121 PoissonDisk16,
1122}
1123
1124impl Default for ShadowConfig {
1125 fn default() -> Self {
1126 Self {
1127 max_distance: 200.0,
1128 fade_start: 150.0,
1129 atlas_resolution: 4096,
1130 default_resolution: 1024,
1131 bias: ShadowBias::default(),
1132 pcf_mode: PcfMode::Pcf3x3,
1133 use_vsm: false,
1134 max_shadow_casters: 16,
1135 cull_shadow_casters: true,
1136 }
1137 }
1138}
1139
1140impl ShadowConfig {
1141 pub fn distance_fade(&self, distance: f32) -> f32 {
1143 if distance >= self.max_distance {
1144 return 0.0;
1145 }
1146 if distance <= self.fade_start {
1147 return 1.0;
1148 }
1149 let range = self.max_distance - self.fade_start;
1150 if range <= 0.0 {
1151 return 0.0;
1152 }
1153 1.0 - (distance - self.fade_start) / range
1154 }
1155
1156 pub fn create_pcf_kernel(&self) -> PcfKernel {
1158 let texel_size = 1.0 / self.default_resolution as f32;
1159 match self.pcf_mode {
1160 PcfMode::None => PcfKernel {
1161 offsets: vec![(0.0, 0.0)],
1162 weights: vec![1.0],
1163 texel_size,
1164 },
1165 PcfMode::Pcf3x3 => PcfKernel::kernel_3x3(texel_size),
1166 PcfMode::Pcf5x5 => PcfKernel::kernel_5x5(texel_size),
1167 PcfMode::PoissonDisk16 => PcfKernel::poisson_disk(16, texel_size),
1168 }
1169 }
1170}
1171
1172#[derive(Debug, Clone, Copy)]
1176pub struct CasterBounds {
1177 pub min: Vec3,
1178 pub max: Vec3,
1179}
1180
1181impl CasterBounds {
1182 pub fn new(min: Vec3, max: Vec3) -> Self {
1183 Self { min, max }
1184 }
1185
1186 pub fn intersects_frustum(&self, frustum_planes: &[(Vec3, f32); 6]) -> bool {
1188 for &(normal, dist) in frustum_planes {
1189 let p = Vec3::new(
1190 if normal.x >= 0.0 { self.max.x } else { self.min.x },
1191 if normal.y >= 0.0 { self.max.y } else { self.min.y },
1192 if normal.z >= 0.0 { self.max.z } else { self.min.z },
1193 );
1194 if normal.dot(p) + dist < 0.0 {
1195 return false;
1196 }
1197 }
1198 true
1199 }
1200
1201 pub fn intersects_sphere(&self, center: Vec3, radius: f32) -> bool {
1203 let mut dist_sq = 0.0f32;
1204
1205 if center.x < self.min.x {
1206 let d = self.min.x - center.x;
1207 dist_sq += d * d;
1208 } else if center.x > self.max.x {
1209 let d = center.x - self.max.x;
1210 dist_sq += d * d;
1211 }
1212
1213 if center.y < self.min.y {
1214 let d = self.min.y - center.y;
1215 dist_sq += d * d;
1216 } else if center.y > self.max.y {
1217 let d = center.y - self.max.y;
1218 dist_sq += d * d;
1219 }
1220
1221 if center.z < self.min.z {
1222 let d = self.min.z - center.z;
1223 dist_sq += d * d;
1224 } else if center.z > self.max.z {
1225 let d = center.z - self.max.z;
1226 dist_sq += d * d;
1227 }
1228
1229 dist_sq <= radius * radius
1230 }
1231}
1232
1233pub fn cull_shadow_casters(
1235 casters: &[CasterBounds],
1236 light: &Light,
1237) -> Vec<usize> {
1238 let mut visible = Vec::new();
1239
1240 match light {
1241 Light::Point(pl) => {
1242 for (i, caster) in casters.iter().enumerate() {
1243 if caster.intersects_sphere(pl.position, pl.radius) {
1244 visible.push(i);
1245 }
1246 }
1247 }
1248 Light::Spot(sl) => {
1249 for (i, caster) in casters.iter().enumerate() {
1251 if caster.intersects_sphere(sl.position, sl.radius) {
1252 visible.push(i);
1253 }
1254 }
1255 }
1256 Light::Directional(_) => {
1257 for i in 0..casters.len() {
1261 visible.push(i);
1262 }
1263 }
1264 _ => {
1265 }
1267 }
1268
1269 visible
1270}
1271
1272#[derive(Debug)]
1276pub struct ShadowSystem {
1277 pub config: ShadowConfig,
1278 pub atlas: ShadowAtlas,
1279 pub cascaded_maps: HashMap<LightId, CascadedShadowMap>,
1280 pub omni_maps: HashMap<LightId, OmniShadowMap>,
1281 pub spot_maps: HashMap<LightId, usize>, pub pcf_kernel: PcfKernel,
1283 pub vsm_maps: HashMap<LightId, VarianceShadowMap>,
1284 pub stats: ShadowStats,
1286}
1287
1288#[derive(Debug, Clone, Default)]
1290pub struct ShadowStats {
1291 pub shadow_casters: u32,
1292 pub cascaded_maps: u32,
1293 pub omni_maps: u32,
1294 pub spot_maps: u32,
1295 pub atlas_utilization: f32,
1296 pub total_memory_bytes: usize,
1297}
1298
1299impl ShadowSystem {
1300 pub fn new(config: ShadowConfig) -> Self {
1301 let pcf_kernel = config.create_pcf_kernel();
1302 let atlas_res = config.atlas_resolution;
1303 Self {
1304 config,
1305 atlas: ShadowAtlas::new(atlas_res, atlas_res),
1306 cascaded_maps: HashMap::new(),
1307 omni_maps: HashMap::new(),
1308 spot_maps: HashMap::new(),
1309 pcf_kernel,
1310 vsm_maps: HashMap::new(),
1311 stats: ShadowStats::default(),
1312 }
1313 }
1314
1315 pub fn allocate_for_lights(&mut self, lights: &[(LightId, &Light)]) {
1317 self.atlas.reset_allocations();
1318 self.atlas.clear();
1319 self.cascaded_maps.clear();
1320 self.omni_maps.clear();
1321 self.spot_maps.clear();
1322 self.vsm_maps.clear();
1323
1324 let mut caster_count = 0u32;
1325
1326 for &(id, light) in lights {
1327 if caster_count >= self.config.max_shadow_casters {
1328 break;
1329 }
1330 if !light.is_enabled() || !light.casts_shadows() {
1331 continue;
1332 }
1333
1334 match light {
1335 Light::Directional(dl) => {
1336 let csm = CascadedShadowMap::new(
1337 dl.cascade_params.resolution,
1338 dl.cascade_params.cascade_count,
1339 );
1340 self.cascaded_maps.insert(id, csm);
1341 caster_count += 1;
1342 }
1343 Light::Point(pl) => {
1344 let res = self.config.default_resolution.min(512);
1345 let osm = OmniShadowMap::new(res, pl.position, pl.radius);
1346 self.omni_maps.insert(id, osm);
1347 caster_count += 1;
1348 }
1349 Light::Spot(_) => {
1350 let res = self.config.default_resolution;
1351 if let Some(region_idx) = self.atlas.allocate(res, res, id) {
1352 self.spot_maps.insert(id, region_idx);
1353 }
1354 caster_count += 1;
1355 }
1356 _ => {}
1357 }
1358
1359 if self.config.use_vsm {
1361 let res = self.config.default_resolution;
1362 let vsm = VarianceShadowMap::new(res, res);
1363 self.vsm_maps.insert(id, vsm);
1364 }
1365 }
1366
1367 self.update_stats();
1368 }
1369
1370 pub fn shadow_factor(
1372 &self,
1373 light_id: LightId,
1374 world_pos: Vec3,
1375 normal: Vec3,
1376 view_depth: f32,
1377 ) -> f32 {
1378 let fade = self.config.distance_fade(view_depth);
1380 if fade <= 0.0 {
1381 return 1.0;
1382 }
1383
1384 let biased_pos = self.config.bias.apply_normal_offset(world_pos, normal);
1385 let effective_bias = self.config.bias.compute(0.0, 0.0);
1386
1387 if self.config.use_vsm {
1389 if let Some(vsm) = self.vsm_maps.get(&light_id) {
1390 let factor = vsm.shadow_factor(biased_pos);
1391 return 1.0 - (1.0 - factor) * fade;
1392 }
1393 }
1394
1395 if let Some(csm) = self.cascaded_maps.get(&light_id) {
1397 let factor = csm.shadow_factor(biased_pos, view_depth, &self.config.bias);
1398 return 1.0 - (1.0 - factor) * fade;
1399 }
1400
1401 if let Some(osm) = self.omni_maps.get(&light_id) {
1403 let factor = osm.shadow_factor_pcf(biased_pos, effective_bias, &self.pcf_kernel);
1404 return 1.0 - (1.0 - factor) * fade;
1405 }
1406
1407 if let Some(®ion_idx) = self.spot_maps.get(&light_id) {
1409 if let Some(region) = self.atlas.regions.get(region_idx) {
1410 let _ = region; return 1.0;
1413 }
1414 }
1415
1416 1.0 }
1418
1419 pub fn combined_shadow_factor(
1421 &self,
1422 world_pos: Vec3,
1423 normal: Vec3,
1424 view_depth: f32,
1425 ) -> f32 {
1426 let mut min_factor = 1.0f32;
1427
1428 for &id in self.cascaded_maps.keys() {
1429 let f = self.shadow_factor(id, world_pos, normal, view_depth);
1430 min_factor = min_factor.min(f);
1431 }
1432 for &id in self.omni_maps.keys() {
1433 let f = self.shadow_factor(id, world_pos, normal, view_depth);
1434 min_factor = min_factor.min(f);
1435 }
1436 for &id in self.spot_maps.keys() {
1437 let f = self.shadow_factor(id, world_pos, normal, view_depth);
1438 min_factor = min_factor.min(f);
1439 }
1440
1441 min_factor
1442 }
1443
1444 fn update_stats(&mut self) {
1445 let mut total_mem = self.atlas.memory_bytes();
1446 for csm in self.cascaded_maps.values() {
1447 total_mem += csm.memory_bytes();
1448 }
1449 for osm in self.omni_maps.values() {
1450 total_mem += osm.memory_bytes();
1451 }
1452 for vsm in self.vsm_maps.values() {
1453 total_mem += vsm.memory_bytes();
1454 }
1455
1456 self.stats = ShadowStats {
1457 shadow_casters: (self.cascaded_maps.len() + self.omni_maps.len() + self.spot_maps.len()) as u32,
1458 cascaded_maps: self.cascaded_maps.len() as u32,
1459 omni_maps: self.omni_maps.len() as u32,
1460 spot_maps: self.spot_maps.len() as u32,
1461 atlas_utilization: self.atlas.utilization(),
1462 total_memory_bytes: total_mem,
1463 };
1464 }
1465
1466 pub fn stats(&self) -> &ShadowStats {
1468 &self.stats
1469 }
1470}
1471
1472#[cfg(test)]
1473mod tests {
1474 use super::*;
1475
1476 #[test]
1477 fn test_shadow_map_depth() {
1478 let mut sm = ShadowMap::new(64, 64);
1479 sm.write_depth(10, 10, 0.5);
1480 assert!((sm.read_depth(10, 10) - 0.5).abs() < 1e-5);
1481 sm.write_depth(10, 10, 0.3);
1483 assert!((sm.read_depth(10, 10) - 0.3).abs() < 1e-5);
1484 sm.write_depth(10, 10, 0.8);
1486 assert!((sm.read_depth(10, 10) - 0.3).abs() < 1e-5);
1487 }
1488
1489 #[test]
1490 fn test_shadow_map_clear() {
1491 let mut sm = ShadowMap::new(16, 16);
1492 sm.write_depth(5, 5, 0.2);
1493 sm.clear();
1494 assert!((sm.read_depth(5, 5) - 1.0).abs() < 1e-5);
1495 }
1496
1497 #[test]
1498 fn test_cascaded_cascade_selection() {
1499 let csm = CascadedShadowMap::new(512, 4);
1500 assert_eq!(csm.select_cascade(5.0), 0);
1501 assert_eq!(csm.select_cascade(20.0), 1);
1502 assert_eq!(csm.select_cascade(50.0), 2);
1503 assert_eq!(csm.select_cascade(100.0), 3);
1504 }
1505
1506 #[test]
1507 fn test_omni_face_selection() {
1508 assert_eq!(
1509 OmniShadowMap::select_face(Vec3::new(1.0, 0.0, 0.0)),
1510 CubeFace::PositiveX
1511 );
1512 assert_eq!(
1513 OmniShadowMap::select_face(Vec3::new(0.0, -1.0, 0.0)),
1514 CubeFace::NegativeY
1515 );
1516 assert_eq!(
1517 OmniShadowMap::select_face(Vec3::new(0.0, 0.0, -1.0)),
1518 CubeFace::NegativeZ
1519 );
1520 }
1521
1522 #[test]
1523 fn test_shadow_atlas_allocation() {
1524 let mut atlas = ShadowAtlas::new(2048, 2048);
1525 let id1 = LightId(1);
1526 let id2 = LightId(2);
1527
1528 let r1 = atlas.allocate(512, 512, id1);
1529 assert!(r1.is_some());
1530
1531 let r2 = atlas.allocate(512, 512, id2);
1532 assert!(r2.is_some());
1533
1534 assert_eq!(atlas.region_count(), 2);
1535 assert!(atlas.utilization() > 0.0);
1536 }
1537
1538 #[test]
1539 fn test_pcf_kernel_weights() {
1540 let kernel = PcfKernel::kernel_3x3(1.0 / 512.0);
1541 let sum: f32 = kernel.weights.iter().sum();
1542 assert!((sum - 1.0).abs() < 1e-4);
1543 assert_eq!(kernel.offsets.len(), 9);
1544 }
1545
1546 #[test]
1547 fn test_vsm_shadow_factor() {
1548 let mut vsm = VarianceShadowMap::new(64, 64);
1549 vsm.view_projection = Mat4::IDENTITY;
1550 let factor = vsm.shadow_factor(Vec3::new(0.0, 0.0, 0.5));
1552 assert!(factor > 0.0);
1553 }
1554
1555 #[test]
1556 fn test_shadow_bias() {
1557 let bias = ShadowBias::new(0.005, 2.0, 0.03);
1558 let computed = bias.compute(0.01, 0.8);
1559 assert!(computed > 0.005); }
1561
1562 #[test]
1563 fn test_caster_bounds_sphere() {
1564 let bounds = CasterBounds::new(
1565 Vec3::new(-1.0, -1.0, -1.0),
1566 Vec3::new(1.0, 1.0, 1.0),
1567 );
1568 assert!(bounds.intersects_sphere(Vec3::ZERO, 2.0));
1569 assert!(!bounds.intersects_sphere(Vec3::new(10.0, 10.0, 10.0), 1.0));
1570 }
1571
1572 #[test]
1573 fn test_shadow_distance_fade() {
1574 let config = ShadowConfig {
1575 max_distance: 200.0,
1576 fade_start: 150.0,
1577 ..Default::default()
1578 };
1579 assert!((config.distance_fade(100.0) - 1.0).abs() < 1e-5);
1580 assert!((config.distance_fade(200.0)).abs() < 1e-5);
1581 assert!(config.distance_fade(175.0) > 0.0 && config.distance_fade(175.0) < 1.0);
1582 }
1583}