rg3d/utils/
lightmap.rs

1//! Module to generate lightmaps for surfaces.
2//!
3//! # Performance
4//!
5//! This is CPU lightmapper, its performance is linear with core count of your CPU.
6//!
7//! WARNING: There is still work-in-progress, so it is not advised to use lightmapper
8//! now!
9
10#![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///
48#[derive(Default, Clone, Debug)]
49pub struct LightmapEntry {
50    /// Lightmap texture.
51    ///
52    /// TODO: Is single texture enough? There may be surfaces with huge amount of faces
53    ///  which may not fit into texture, because there is hardware limit on most GPUs
54    ///  up to 8192x8192 pixels.
55    pub texture: Option<Texture>,
56    /// List of lights that were used to generate this lightmap. This list is used for
57    /// masking when applying dynamic lights for surfaces with light, it prevents double
58    /// lighting.
59    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/// Lightmap is a texture with precomputed lighting.
74#[derive(Default, Clone, Debug)]
75pub struct Lightmap {
76    /// Node handle to lightmap mapping. It is used to quickly get information about
77    /// lightmaps for any node in scene.
78    pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
79
80    /// List of surface data patches. Each patch will be applied to corresponding
81    /// surface data on resolve stage.
82    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    /// World-space vertices.
104    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/// Small helper that allows you stop lightmap generation in any time.
123#[derive(Clone, Default)]
124pub struct CancellationToken(pub Arc<AtomicBool>);
125
126impl CancellationToken {
127    /// Creates new cancellation token.
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    /// Checks if generation was cancelled.
133    pub fn is_cancelled(&self) -> bool {
134        self.0.load(atomic::Ordering::SeqCst)
135    }
136
137    /// Raises cancellation flag, actual cancellation is not immediate!
138    pub fn cancel(&self) {
139        self.0.store(true, atomic::Ordering::SeqCst)
140    }
141}
142
143/// Lightmap generation stage.
144#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
145#[repr(u32)]
146pub enum ProgressStage {
147    /// Gathering info about lights, doing precalculations.
148    LightsCaching = 0,
149    /// Generating secondary texture coordinates.
150    UvGeneration = 1,
151    /// Caching geometry, building octrees.
152    GeometryCaching = 2,
153    /// Actual lightmap generation.
154    CalculatingLight = 3,
155}
156
157/// Progress internals.
158#[derive(Default)]
159pub struct ProgressData {
160    stage: AtomicU32,
161    // Range is [0; max_iterations]
162    progress: AtomicU32,
163    max_iterations: AtomicU32,
164}
165
166impl ProgressData {
167    /// Returns progress percentage in [0; 100] range.
168    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    /// Returns current stage.
178    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    /// Sets new stage with max iterations per stage.
189    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    /// Advances progress.
197    fn advance_progress(&self) {
198        self.progress.fetch_add(1, atomic::Ordering::SeqCst);
199    }
200}
201
202/// Small helper that allows you to track progress of lightmap generation.
203#[derive(Clone, Default)]
204pub struct ProgressIndicator(pub Arc<ProgressData>);
205
206impl ProgressIndicator {
207    /// Creates new progress indicator.
208    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/// An error that may occur during ligthmap generation.
222#[derive(Debug, thiserror::Error)]
223pub enum LightmapGenerationError {
224    /// Generation was cancelled by user.
225    #[error("Lightmap generation was cancelled by the user.")]
226    Cancelled,
227    /// Vertex buffer of a mesh lacks required data.
228    #[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    /// Generates lightmap for given scene. This method **automatically** generates secondary
240    /// texture coordinates! This method is blocking, however internally it uses massive parallelism
241    /// to use all available CPU power efficiently.
242    ///
243    /// `texels_per_unit` defines resolution of lightmap, the higher value is, the more quality
244    /// lightmap will be generated, but also it will be slow to generate.
245    /// `progress_indicator` allows you to get info about current progress.
246    /// `cancellation_token` allows you to stop generation in any time.
247    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        // Extract info about lights first. We need it to be in separate array because
256        // it won't be possible to store immutable references to light sources and at the
257        // same time modify meshes. Also it precomputes a lot of things for faster calculations.
258        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                    // Gather unique "list" of surface data to generate UVs for.
330                    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                        // Calculated down below.
339                        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    /// Saves lightmap textures into specified folder.
448    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
469/// Directional light is a light source with parallel rays. Example: Sun.
470pub struct DirectionalLightDefinition {
471    /// A handle of light in the scene.
472    pub handle: Handle<Node>,
473    /// Intensity is how bright light is. Default is 1.0.
474    pub intensity: f32,
475    /// Direction of light rays.
476    pub direction: Vector3<f32>,
477    /// Color of light.
478    pub color: Vector3<f32>,
479}
480
481/// Spot light is a cone light source. Example: flashlight.
482pub struct SpotLightDefinition {
483    /// A handle of light in the scene.
484    pub handle: Handle<Node>,
485    /// Intensity is how bright light is. Default is 1.0.
486    pub intensity: f32,
487    /// Color of light.
488    pub color: Vector3<f32>,
489    /// Direction vector of light.
490    pub direction: Vector3<f32>,
491    /// Position of light in world coordinates.
492    pub position: Vector3<f32>,
493    /// Distance at which light intensity decays to zero.
494    pub distance: f32,
495    /// Square of distance.
496    pub sqr_distance: f32,
497    /// Smoothstep left bound. It is ((hotspot_cone_angle + falloff_angle_delta) * 0.5).cos()
498    pub edge0: f32,
499    /// Smoothstep right bound. It is (hotspot_cone_angle * 0.5).cos()
500    pub edge1: f32,
501}
502
503/// Point light is a spherical light source. Example: light bulb.
504pub struct PointLightDefinition {
505    /// A handle of light in the scene.
506    pub handle: Handle<Node>,
507    /// Intensity is how bright light is. Default is 1.0.
508    pub intensity: f32,
509    /// Position of light in world coordinates.
510    pub position: Vector3<f32>,
511    /// Color of light.
512    pub color: Vector3<f32>,
513    /// Radius of sphere at which light intensity decays to zero.
514    pub radius: f32,
515    /// Square of radius.
516    pub sqr_radius: f32,
517}
518
519/// Light definition for lightmap rendering.
520pub enum LightDefinition {
521    /// See docs of [DirectionalLightDefinition](struct.PointLightDefinition.html)
522    Directional(DirectionalLightDefinition),
523    /// See docs of [SpotLightDefinition](struct.SpotLightDefinition.html)
524    Spot(SpotLightDefinition),
525    /// See docs of [PointLightDefinition](struct.PointLightDefinition.html)
526    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
539/// Computes total area of triangles in surface data and returns size of square
540/// in which triangles can fit.
541fn 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
552/// Calculates distance attenuation for a point using given distance to the point and
553/// radius of a light.
554fn 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
559/// Calculates properties of pixel (world position, normal) at given position.
560fn 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                // Offset uv to center for conservative rasterization.
602                current_uv += to_center;
603            }
604        }
605    }
606    None
607}
608
609struct GridCell {
610    // List of triangle indices.
611    triangles: Vec<usize>,
612}
613
614struct Grid {
615    cells: Vec<GridCell>,
616    size: usize,
617    fsize: f32,
618}
619
620impl Grid {
621    /// Creates uniform grid where each cell contains list of triangles
622    /// whose second texture coordinates intersects with it.
623    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
664/// https://en.wikipedia.org/wiki/Lambert%27s_cosine_law
665fn lambertian(light_vec: Vector3<f32>, normal: Vector3<f32>) -> f32 {
666    normal.dot(&light_vec).max(0.0)
667}
668
669/// https://en.wikipedia.org/wiki/Smoothstep
670fn 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
675/// Generates lightmap for given surface data with specified transform.
676///
677/// # Performance
678///
679/// This method is has linear complexity - the more complex mesh you pass, the more
680/// time it will take. Required time increases drastically if you enable shadows and
681/// global illumination (TODO), because in this case your data will be raytraced.
682fn generate_lightmap(
683    instance: &Instance,
684    other_instances: &[Instance],
685    lights: &[LightDefinition],
686    texels_per_unit: u32,
687) -> TextureData {
688    // We have to re-generate new set of world-space vertices because UV generator
689    // may add new vertices on seams.
690    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                    // Shadows
739                    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, // Indicates that this pixel was "filled"
786                );
787            }
788        });
789
790    // Prepare light map for bilinear filtration. This step is mandatory to prevent bleeding.
791    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                // Check neighbour pixels marked as "filled" and use it as value.
809                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    // Blur lightmap using simplest box filter.
835    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        // Do not serialize content because lightmap is saved as a series of images in
883        // a common format.
884        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}