1#![forbid(unsafe_code)]
11
12use crate::{
13 asset::Resource,
14 core::{
15 algebra::{Matrix3, Matrix4, Point3, Vector2, Vector3, Vector4},
16 arrayvec::ArrayVec,
17 math::{self, ray::Ray, Matrix4Ext, Rect, TriangleDefinition, Vector2Ext},
18 octree::{Octree, OctreeNode},
19 parking_lot::Mutex,
20 pool::Handle,
21 visitor::{Visit, VisitResult, Visitor},
22 },
23 engine::resource_manager::{ResourceManager, TextureRegistrationError},
24 resource::texture::{Texture, TextureData, TextureKind, TexturePixelKind, TextureState},
25 scene::{
26 light::Light,
27 mesh::{
28 buffer::{VertexAttributeUsage, VertexFetchError, VertexReadTrait},
29 surface::SurfaceData,
30 },
31 node::Node,
32 Scene,
33 },
34 utils::{uvgen, uvgen::SurfaceDataPatch},
35};
36use fxhash::FxHashMap;
37use rayon::prelude::*;
38use std::{
39 ops::Deref,
40 path::Path,
41 sync::{
42 atomic::{self, AtomicBool, AtomicU32},
43 Arc,
44 },
45};
46
47#[derive(Default, Clone, Debug)]
49pub struct LightmapEntry {
50 pub texture: Option<Texture>,
56 pub lights: Vec<Handle<Node>>,
60}
61
62impl Visit for LightmapEntry {
63 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
64 visitor.enter_region(name)?;
65
66 self.texture.visit("Texture", visitor)?;
67 self.lights.visit("Lights", visitor)?;
68
69 visitor.leave_region()
70 }
71}
72
73#[derive(Default, Clone, Debug)]
75pub struct Lightmap {
76 pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
79
80 pub patches: FxHashMap<u64, SurfaceDataPatch>,
83}
84
85impl Visit for Lightmap {
86 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
87 visitor.enter_region(name)?;
88
89 self.map.visit("Map", visitor)?;
90 self.patches.visit("Patches", visitor)?;
91
92 visitor.leave_region()
93 }
94}
95
96struct WorldVertex {
97 world_normal: Vector3<f32>,
98 world_position: Vector3<f32>,
99 second_tex_coord: Vector2<f32>,
100}
101
102struct InstanceData {
103 vertices: Vec<WorldVertex>,
105 triangles: Vec<TriangleDefinition>,
106 octree: Octree,
107}
108
109struct Instance {
110 owner: Handle<Node>,
111 source_data: Arc<Mutex<SurfaceData>>,
112 data: Option<InstanceData>,
113 transform: Matrix4<f32>,
114}
115
116impl Instance {
117 pub fn data(&self) -> &InstanceData {
118 self.data.as_ref().unwrap()
119 }
120}
121
122#[derive(Clone, Default)]
124pub struct CancellationToken(pub Arc<AtomicBool>);
125
126impl CancellationToken {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn is_cancelled(&self) -> bool {
134 self.0.load(atomic::Ordering::SeqCst)
135 }
136
137 pub fn cancel(&self) {
139 self.0.store(true, atomic::Ordering::SeqCst)
140 }
141}
142
143#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
145#[repr(u32)]
146pub enum ProgressStage {
147 LightsCaching = 0,
149 UvGeneration = 1,
151 GeometryCaching = 2,
153 CalculatingLight = 3,
155}
156
157#[derive(Default)]
159pub struct ProgressData {
160 stage: AtomicU32,
161 progress: AtomicU32,
163 max_iterations: AtomicU32,
164}
165
166impl ProgressData {
167 pub fn progress_percent(&self) -> u32 {
169 let iterations = self.max_iterations.load(atomic::Ordering::SeqCst);
170 if iterations > 0 {
171 self.progress.load(atomic::Ordering::SeqCst) * 100 / iterations
172 } else {
173 0
174 }
175 }
176
177 pub fn stage(&self) -> ProgressStage {
179 match self.stage.load(atomic::Ordering::SeqCst) {
180 0 => ProgressStage::LightsCaching,
181 1 => ProgressStage::UvGeneration,
182 2 => ProgressStage::GeometryCaching,
183 3 => ProgressStage::CalculatingLight,
184 _ => unreachable!(),
185 }
186 }
187
188 fn set_stage(&self, stage: ProgressStage, max_iterations: u32) {
190 self.max_iterations
191 .store(max_iterations, atomic::Ordering::SeqCst);
192 self.progress.store(0, atomic::Ordering::SeqCst);
193 self.stage.store(stage as u32, atomic::Ordering::SeqCst);
194 }
195
196 fn advance_progress(&self) {
198 self.progress.fetch_add(1, atomic::Ordering::SeqCst);
199 }
200}
201
202#[derive(Clone, Default)]
204pub struct ProgressIndicator(pub Arc<ProgressData>);
205
206impl ProgressIndicator {
207 pub fn new() -> Self {
209 Self::default()
210 }
211}
212
213impl Deref for ProgressIndicator {
214 type Target = ProgressData;
215
216 fn deref(&self) -> &Self::Target {
217 &*self.0
218 }
219}
220
221#[derive(Debug, thiserror::Error)]
223pub enum LightmapGenerationError {
224 #[error("Lightmap generation was cancelled by the user.")]
226 Cancelled,
227 #[error("Vertex buffer of a mesh lacks required data {0}.")]
229 InvalidData(VertexFetchError),
230}
231
232impl From<VertexFetchError> for LightmapGenerationError {
233 fn from(e: VertexFetchError) -> Self {
234 Self::InvalidData(e)
235 }
236}
237
238impl Lightmap {
239 pub fn new(
248 scene: &mut Scene,
249 texels_per_unit: u32,
250 cancellation_token: CancellationToken,
251 progress_indicator: ProgressIndicator,
252 ) -> Result<Self, LightmapGenerationError> {
253 scene.graph.update_hierarchical_data();
254
255 let mut light_count = 0;
259 for node in scene.graph.linear_iter() {
260 if matches!(node, Node::Light(_)) {
261 light_count += 1;
262 }
263 }
264
265 progress_indicator.set_stage(ProgressStage::LightsCaching, light_count);
266
267 let mut lights = Vec::with_capacity(light_count as usize);
268
269 for (handle, light) in scene.graph.pair_iter().filter_map(|(h, n)| {
270 if let Node::Light(light) = n {
271 Some((h, light))
272 } else {
273 None
274 }
275 }) {
276 if cancellation_token.is_cancelled() {
277 return Err(LightmapGenerationError::Cancelled);
278 }
279
280 match light {
281 Light::Directional(_) => {
282 lights.push(LightDefinition::Directional(DirectionalLightDefinition {
283 handle,
284 intensity: 1.0,
285 direction: light
286 .up_vector()
287 .try_normalize(std::f32::EPSILON)
288 .unwrap_or_else(Vector3::y),
289 color: light.color().srgb_to_linear().as_frgb(),
290 }))
291 }
292 Light::Spot(spot) => lights.push(LightDefinition::Spot(SpotLightDefinition {
293 handle,
294 intensity: 1.0,
295 edge0: ((spot.hotspot_cone_angle() + spot.falloff_angle_delta()) * 0.5).cos(),
296 edge1: (spot.hotspot_cone_angle() * 0.5).cos(),
297 color: light.color().srgb_to_linear().as_frgb(),
298 direction: light
299 .up_vector()
300 .try_normalize(std::f32::EPSILON)
301 .unwrap_or_else(Vector3::y),
302 position: light.global_position(),
303 distance: spot.distance(),
304 sqr_distance: spot.distance() * spot.distance(),
305 })),
306 Light::Point(point) => lights.push(LightDefinition::Point(PointLightDefinition {
307 handle,
308 intensity: 1.0,
309 position: light.global_position(),
310 color: light.color().srgb_to_linear().as_frgb(),
311 radius: point.radius(),
312 sqr_radius: point.radius() * point.radius(),
313 })),
314 }
315
316 progress_indicator.advance_progress()
317 }
318
319 let mut instances = Vec::new();
320 let mut data_set = FxHashMap::default();
321
322 for (handle, node) in scene.graph.pair_iter() {
323 if let Node::Mesh(mesh) = node {
324 if !mesh.global_visibility() {
325 continue;
326 }
327 let global_transform = mesh.global_transform();
328 for surface in mesh.surfaces() {
329 let data = surface.data();
331 let key = &*data.lock() as *const _ as u64;
332 data_set.entry(key).or_insert_with(|| surface.data());
333
334 instances.push(Instance {
335 owner: handle,
336 source_data: data.clone(),
337 transform: global_transform,
338 data: None,
340 });
341 }
342 }
343 }
344
345 progress_indicator.set_stage(ProgressStage::UvGeneration, data_set.len() as u32);
346
347 let patches = data_set
348 .into_par_iter()
349 .map(|(_, data)| {
350 if cancellation_token.is_cancelled() {
351 Err(LightmapGenerationError::Cancelled)
352 } else {
353 let mut data = data.lock();
354 let patch = uvgen::generate_uvs(&mut data, 0.005)?;
355 progress_indicator.advance_progress();
356 Ok((patch.data_id, patch))
357 }
358 })
359 .collect::<Result<FxHashMap<_, _>, LightmapGenerationError>>()?;
360
361 progress_indicator.set_stage(ProgressStage::GeometryCaching, instances.len() as u32);
362
363 instances
364 .par_iter_mut()
365 .map(|instance: &mut Instance| {
366 if cancellation_token.is_cancelled() {
367 Err(LightmapGenerationError::Cancelled)
368 } else {
369 let data = instance.source_data.lock();
370
371 let normal_matrix = instance
372 .transform
373 .basis()
374 .try_inverse()
375 .map(|m| m.transpose())
376 .unwrap_or_else(Matrix3::identity);
377
378 let world_vertices = data
379 .vertex_buffer
380 .iter()
381 .map(|view| {
382 let world_position = instance
383 .transform
384 .transform_point(&Point3::from(
385 view.read_3_f32(VertexAttributeUsage::Position).unwrap(),
386 ))
387 .coords;
388 let world_normal = (normal_matrix
389 * view.read_3_f32(VertexAttributeUsage::Normal).unwrap())
390 .try_normalize(f32::EPSILON)
391 .unwrap_or_default();
392 WorldVertex {
393 world_normal,
394 world_position,
395 second_tex_coord: view
396 .read_2_f32(VertexAttributeUsage::TexCoord1)
397 .unwrap(),
398 }
399 })
400 .collect::<Vec<_>>();
401
402 let world_triangles = data
403 .geometry_buffer
404 .iter()
405 .map(|tri| {
406 [
407 world_vertices[tri[0] as usize].world_position,
408 world_vertices[tri[1] as usize].world_position,
409 world_vertices[tri[2] as usize].world_position,
410 ]
411 })
412 .collect::<Vec<_>>();
413
414 instance.data = Some(InstanceData {
415 vertices: world_vertices,
416 triangles: data.geometry_buffer.triangles_ref().to_vec(),
417 octree: Octree::new(&world_triangles, 64),
418 });
419
420 progress_indicator.advance_progress();
421
422 Ok(())
423 }
424 })
425 .collect::<Result<(), LightmapGenerationError>>()?;
426
427 progress_indicator.set_stage(ProgressStage::CalculatingLight, instances.len() as u32);
428
429 let mut map: FxHashMap<Handle<Node>, Vec<LightmapEntry>> = FxHashMap::default();
430 for instance in instances.iter() {
431 if cancellation_token.is_cancelled() {
432 return Err(LightmapGenerationError::Cancelled);
433 }
434
435 let lightmap = generate_lightmap(instance, &instances, &lights, texels_per_unit);
436 map.entry(instance.owner).or_default().push(LightmapEntry {
437 texture: Some(Texture(Resource::new(TextureState::Ok(lightmap)))),
438 lights: lights.iter().map(|light| light.handle()).collect(),
439 });
440
441 progress_indicator.advance_progress();
442 }
443
444 Ok(Self { map, patches })
445 }
446
447 pub fn save<P: AsRef<Path>>(
449 &self,
450 base_path: P,
451 resource_manager: ResourceManager,
452 ) -> Result<(), TextureRegistrationError> {
453 if !base_path.as_ref().exists() {
454 std::fs::create_dir(base_path.as_ref()).unwrap();
455 }
456
457 for (handle, entries) in self.map.iter() {
458 let handle_path = handle.index().to_string();
459 for (i, entry) in entries.iter().enumerate() {
460 let file_path = handle_path.clone() + "_" + i.to_string().as_str() + ".png";
461 let texture = entry.texture.clone().unwrap();
462 resource_manager.register_texture(texture, base_path.as_ref().join(file_path))?;
463 }
464 }
465 Ok(())
466 }
467}
468
469pub struct DirectionalLightDefinition {
471 pub handle: Handle<Node>,
473 pub intensity: f32,
475 pub direction: Vector3<f32>,
477 pub color: Vector3<f32>,
479}
480
481pub struct SpotLightDefinition {
483 pub handle: Handle<Node>,
485 pub intensity: f32,
487 pub color: Vector3<f32>,
489 pub direction: Vector3<f32>,
491 pub position: Vector3<f32>,
493 pub distance: f32,
495 pub sqr_distance: f32,
497 pub edge0: f32,
499 pub edge1: f32,
501}
502
503pub struct PointLightDefinition {
505 pub handle: Handle<Node>,
507 pub intensity: f32,
509 pub position: Vector3<f32>,
511 pub color: Vector3<f32>,
513 pub radius: f32,
515 pub sqr_radius: f32,
517}
518
519pub enum LightDefinition {
521 Directional(DirectionalLightDefinition),
523 Spot(SpotLightDefinition),
525 Point(PointLightDefinition),
527}
528
529impl LightDefinition {
530 fn handle(&self) -> Handle<Node> {
531 match self {
532 LightDefinition::Directional(v) => v.handle,
533 LightDefinition::Spot(v) => v.handle,
534 LightDefinition::Point(v) => v.handle,
535 }
536 }
537}
538
539fn estimate_size(data: &InstanceData, texels_per_unit: u32) -> u32 {
542 let mut area = 0.0;
543 for triangle in data.triangles.iter() {
544 let a = data.vertices[triangle[0] as usize].world_position;
545 let b = data.vertices[triangle[1] as usize].world_position;
546 let c = data.vertices[triangle[2] as usize].world_position;
547 area += math::triangle_area(a, b, c);
548 }
549 area.sqrt().ceil() as u32 * texels_per_unit
550}
551
552fn distance_attenuation(distance: f32, sqr_radius: f32) -> f32 {
555 let attenuation = (1.0 - distance * distance / sqr_radius).max(0.0).min(1.0);
556 attenuation * attenuation
557}
558
559fn pick(
561 uv: Vector2<f32>,
562 grid: &Grid,
563 data: &InstanceData,
564 scale: f32,
565) -> Option<(Vector3<f32>, Vector3<f32>)> {
566 if let Some(cell) = grid.pick(uv) {
567 for triangle in cell.triangles.iter().map(|&ti| &data.triangles[ti]) {
568 let ia = triangle[0] as usize;
569 let ib = triangle[1] as usize;
570 let ic = triangle[2] as usize;
571
572 let uv_a = data.vertices[ia].second_tex_coord;
573 let uv_b = data.vertices[ib].second_tex_coord;
574 let uv_c = data.vertices[ic].second_tex_coord;
575
576 let center = (uv_a + uv_b + uv_c).scale(1.0 / 3.0);
577 let to_center = (center - uv)
578 .try_normalize(std::f32::EPSILON)
579 .unwrap_or_default()
580 .scale(scale * 0.3333333);
581
582 let mut current_uv = uv;
583 for _ in 0..3 {
584 let barycentric = math::get_barycentric_coords_2d(current_uv, uv_a, uv_b, uv_c);
585
586 if math::barycentric_is_inside(barycentric) {
587 let a = data.vertices[ia].world_position;
588 let b = data.vertices[ib].world_position;
589 let c = data.vertices[ic].world_position;
590
591 let na = data.vertices[ia].world_normal;
592 let nb = data.vertices[ib].world_normal;
593 let nc = data.vertices[ic].world_normal;
594
595 return Some((
596 math::barycentric_to_world(barycentric, a, b, c),
597 math::barycentric_to_world(barycentric, na, nb, nc),
598 ));
599 }
600
601 current_uv += to_center;
603 }
604 }
605 }
606 None
607}
608
609struct GridCell {
610 triangles: Vec<usize>,
612}
613
614struct Grid {
615 cells: Vec<GridCell>,
616 size: usize,
617 fsize: f32,
618}
619
620impl Grid {
621 fn new(data: &InstanceData, size: usize) -> Self {
624 let mut cells = Vec::with_capacity(size);
625 let fsize = size as f32;
626 for y in 0..size {
627 for x in 0..size {
628 let bounds =
629 Rect::new(x as f32 / fsize, y as f32 / fsize, 1.0 / fsize, 1.0 / fsize);
630
631 let mut triangles = Vec::new();
632
633 for (triangle_index, triangle) in data.triangles.iter().enumerate() {
634 let uv_a = data.vertices[triangle[0] as usize].second_tex_coord;
635 let uv_b = data.vertices[triangle[1] as usize].second_tex_coord;
636 let uv_c = data.vertices[triangle[2] as usize].second_tex_coord;
637 let uv_min = uv_a.per_component_min(&uv_b).per_component_min(&uv_c);
638 let uv_max = uv_a.per_component_max(&uv_b).per_component_max(&uv_c);
639 let triangle_bounds =
640 Rect::new(uv_min.x, uv_min.y, uv_max.x - uv_min.x, uv_max.y - uv_min.y);
641 if triangle_bounds.intersects(bounds) {
642 triangles.push(triangle_index);
643 }
644 }
645
646 cells.push(GridCell { triangles })
647 }
648 }
649
650 Self {
651 cells,
652 size,
653 fsize: size as f32,
654 }
655 }
656
657 fn pick(&self, v: Vector2<f32>) -> Option<&GridCell> {
658 let ix = (v.x * self.fsize) as usize;
659 let iy = (v.y * self.fsize) as usize;
660 self.cells.get(iy * self.size + ix)
661 }
662}
663
664fn lambertian(light_vec: Vector3<f32>, normal: Vector3<f32>) -> f32 {
666 normal.dot(&light_vec).max(0.0)
667}
668
669fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
671 let k = ((x - edge0) / (edge1 - edge0)).max(0.0).min(1.0);
672 k * k * (3.0 - 2.0 * k)
673}
674
675fn generate_lightmap(
683 instance: &Instance,
684 other_instances: &[Instance],
685 lights: &[LightDefinition],
686 texels_per_unit: u32,
687) -> TextureData {
688 let atlas_size = estimate_size(instance.data(), texels_per_unit);
691 let scale = 1.0 / atlas_size as f32;
692 let grid = Grid::new(instance.data(), (atlas_size / 32).max(4) as usize);
693
694 let mut pixels: Vec<Vector4<u8>> =
695 vec![Vector4::new(0, 0, 0, 0); (atlas_size * atlas_size) as usize];
696
697 let half_pixel = scale * 0.5;
698 pixels
699 .par_iter_mut()
700 .enumerate()
701 .for_each(|(i, pixel): (usize, &mut Vector4<u8>)| {
702 let x = i as u32 % atlas_size;
703 let y = i as u32 / atlas_size;
704
705 let uv = Vector2::new(x as f32 * scale + half_pixel, y as f32 * scale + half_pixel);
706
707 if let Some((world_position, world_normal)) = pick(uv, &grid, instance.data(), scale) {
708 let mut pixel_color = Vector3::default();
709 for light in lights {
710 let (light_color, mut attenuation, light_position) = match light {
711 LightDefinition::Directional(directional) => {
712 let attenuation = directional.intensity
713 * lambertian(directional.direction, world_normal);
714 (directional.color, attenuation, Vector3::default())
715 }
716 LightDefinition::Spot(spot) => {
717 let d = spot.position - world_position;
718 let distance = d.norm();
719 let light_vec = d.scale(1.0 / distance);
720 let spot_angle_cos = light_vec.dot(&spot.direction);
721 let cone_factor = smoothstep(spot.edge0, spot.edge1, spot_angle_cos);
722 let attenuation = cone_factor
723 * spot.intensity
724 * lambertian(light_vec, world_normal)
725 * distance_attenuation(distance, spot.sqr_distance);
726 (spot.color, attenuation, spot.position)
727 }
728 LightDefinition::Point(point) => {
729 let d = point.position - world_position;
730 let distance = d.norm();
731 let light_vec = d.scale(1.0 / distance);
732 let attenuation = point.intensity
733 * lambertian(light_vec, world_normal)
734 * distance_attenuation(distance, point.sqr_radius);
735 (point.color, attenuation, point.position)
736 }
737 };
738 if attenuation >= 0.01 {
740 let mut query_buffer = ArrayVec::<Handle<OctreeNode>, 64>::new();
741 let shadow_bias = 0.01;
742 let ray = Ray::from_two_points(light_position, world_position);
743 'outer_loop: for other_instance in other_instances {
744 other_instance
745 .data()
746 .octree
747 .ray_query_static(&ray, &mut query_buffer);
748 for &node in query_buffer.iter() {
749 match other_instance.data().octree.node(node) {
750 OctreeNode::Leaf { indices, .. } => {
751 let other_data = other_instance.data();
752 for &triangle_index in indices {
753 let triangle =
754 &other_data.triangles[triangle_index as usize];
755 let va = other_data.vertices[triangle[0] as usize]
756 .world_position;
757 let vb = other_data.vertices[triangle[1] as usize]
758 .world_position;
759 let vc = other_data.vertices[triangle[2] as usize]
760 .world_position;
761 if let Some(pt) =
762 ray.triangle_intersection_point(&[va, vb, vc])
763 {
764 if ray.origin.metric_distance(&pt) + shadow_bias
765 < ray.dir.norm()
766 {
767 attenuation = 0.0;
768 break 'outer_loop;
769 }
770 }
771 }
772 }
773 OctreeNode::Branch { .. } => unreachable!(),
774 }
775 }
776 }
777 }
778 pixel_color += light_color.scale(attenuation);
779 }
780
781 *pixel = Vector4::new(
782 (pixel_color.x.max(0.0).min(1.0) * 255.0) as u8,
783 (pixel_color.y.max(0.0).min(1.0) * 255.0) as u8,
784 (pixel_color.z.max(0.0).min(1.0) * 255.0) as u8,
785 255, );
787 }
788 });
789
790 let mut rgb_pixels: Vec<Vector3<u8>> = Vec::with_capacity((atlas_size * atlas_size) as usize);
792 for y in 0..(atlas_size as i32) {
793 for x in 0..(atlas_size as i32) {
794 let fetch = |dx: i32, dy: i32| -> Option<Vector3<u8>> {
795 pixels
796 .get(((y + dy) * (atlas_size as i32) + x + dx) as usize)
797 .and_then(|p| {
798 if p.w != 0 {
799 Some(Vector3::new(p.x, p.y, p.z))
800 } else {
801 None
802 }
803 })
804 };
805
806 let src_pixel = pixels[(y * (atlas_size as i32) + x) as usize];
807 if src_pixel.w == 0 {
808 if let Some(west) = fetch(-1, 0) {
810 rgb_pixels.push(west);
811 } else if let Some(east) = fetch(1, 0) {
812 rgb_pixels.push(east);
813 } else if let Some(north) = fetch(0, -1) {
814 rgb_pixels.push(north);
815 } else if let Some(south) = fetch(0, 1) {
816 rgb_pixels.push(south);
817 } else if let Some(north_west) = fetch(-1, -1) {
818 rgb_pixels.push(north_west);
819 } else if let Some(north_east) = fetch(1, -1) {
820 rgb_pixels.push(north_east);
821 } else if let Some(south_east) = fetch(1, 1) {
822 rgb_pixels.push(south_east);
823 } else if let Some(south_west) = fetch(-1, 1) {
824 rgb_pixels.push(south_west);
825 } else {
826 rgb_pixels.push(Vector3::new(0, 0, 0));
827 }
828 } else {
829 rgb_pixels.push(Vector3::new(src_pixel.x, src_pixel.y, src_pixel.z))
830 }
831 }
832 }
833
834 let mut bytes = Vec::with_capacity((atlas_size * atlas_size * 3) as usize);
836 for y in 0..(atlas_size as i32) {
837 for x in 0..(atlas_size as i32) {
838 if x < 1 || y < 1 || x + 1 == atlas_size as i32 || y + 1 == atlas_size as i32 {
839 bytes.extend_from_slice(
840 rgb_pixels[(y * (atlas_size as i32) + x) as usize].as_slice(),
841 );
842 } else {
843 let fetch = |dx: i32, dy: i32| -> Vector3<i16> {
844 let u8_pixel = rgb_pixels[((y + dy) * (atlas_size as i32) + x + dx) as usize];
845 Vector3::new(u8_pixel.x as i16, u8_pixel.y as i16, u8_pixel.z as i16)
846 };
847
848 let north_west = fetch(-1, -1);
849 let north = fetch(0, -1);
850 let north_east = fetch(1, -1);
851 let west = fetch(-1, 0);
852 let center = fetch(0, 0);
853 let east = fetch(1, 0);
854 let south_west = fetch(-1, 1);
855 let south = fetch(0, 1);
856 let south_east = fetch(-1, 1);
857
858 let sum = north_west
859 + north
860 + north_east
861 + west
862 + center
863 + east
864 + south_west
865 + south
866 + south_east;
867
868 bytes.push((sum.x / 9).max(0).min(255) as u8);
869 bytes.push((sum.y / 9).max(0).min(255) as u8);
870 bytes.push((sum.z / 9).max(0).min(255) as u8);
871 }
872 }
873 }
874
875 TextureData::from_bytes(
876 TextureKind::Rectangle {
877 width: atlas_size,
878 height: atlas_size,
879 },
880 TexturePixelKind::RGB8,
881 bytes,
882 false,
885 )
886 .unwrap()
887}
888
889#[cfg(test)]
890mod test {
891 use crate::{
892 core::{
893 algebra::{Matrix4, Vector3},
894 parking_lot::Mutex,
895 },
896 scene::{
897 base::BaseBuilder,
898 light::{point::PointLightBuilder, BaseLightBuilder},
899 mesh::{
900 surface::{SurfaceBuilder, SurfaceData},
901 MeshBuilder,
902 },
903 transform::TransformBuilder,
904 Scene,
905 },
906 utils::lightmap::Lightmap,
907 };
908 use std::sync::Arc;
909
910 #[test]
911 fn test_generate_lightmap() {
912 let mut scene = Scene::new();
913
914 let data = SurfaceData::make_cone(
915 16,
916 1.0,
917 1.0,
918 &Matrix4::new_nonuniform_scaling(&Vector3::new(1.0, 1.1, 1.0)),
919 );
920
921 MeshBuilder::new(BaseBuilder::new())
922 .with_surfaces(vec![SurfaceBuilder::new(Arc::new(Mutex::new(data))).build()])
923 .build(&mut scene.graph);
924
925 PointLightBuilder::new(BaseLightBuilder::new(
926 BaseBuilder::new().with_local_transform(
927 TransformBuilder::new()
928 .with_local_position(Vector3::new(0.0, 2.0, 0.0))
929 .build(),
930 ),
931 ))
932 .with_radius(4.0)
933 .build(&mut scene.graph);
934
935 let lightmap =
936 Lightmap::new(&mut scene, 64, Default::default(), Default::default()).unwrap();
937
938 let mut counter = 0;
939 for entry_set in lightmap.map.values() {
940 for entry in entry_set {
941 let mut data = entry.texture.as_ref().unwrap().data_ref();
942 data.set_path(format!("{}.png", counter));
943 data.save().unwrap();
944 counter += 1;
945 }
946 }
947 }
948}