lightmap/
lib.rs

1//! Light map generator.
2//!
3//! ## Example
4//!
5//! ```rust
6//! use lightmap::{
7//!     input::{Mesh, WorldVertex},
8//!     light::{LightDefinition, PointLightDefinition},
9//!     LightMap,
10//! };
11//! use nalgebra::{Vector2, Vector3};
12//!
13//! #[derive(Copy, Clone, Debug, Default, PartialEq)]
14//! pub struct Vertex {
15//!     pub position: Vector3<f32>,
16//!     pub tex_coord: Vector2<f32>,
17//! }
18//!
19//! impl Vertex {
20//!     fn new(x: f32, y: f32, z: f32) -> Self {
21//!         Self {
22//!             position: Vector3::new(x, y, z),
23//!             tex_coord: Default::default(),
24//!         }
25//!     }
26//! }
27//!
28//! // Create cube geometry.
29//! let mut vertices = vec![
30//!     Vertex::new(-0.5, -0.5, 0.5),
31//!     Vertex::new(-0.5, 0.5, 0.5),
32//!     Vertex::new(0.5, 0.5, 0.5),
33//!     Vertex::new(0.5, -0.5, 0.5),
34//!     Vertex::new(-0.5, -0.5, -0.5),
35//!     Vertex::new(-0.5, 0.5, -0.5),
36//!     Vertex::new(0.5, 0.5, -0.5),
37//!     Vertex::new(0.5, -0.5, -0.5),
38//! ];
39//! let mut triangles = vec![
40//!     // Front
41//!     [2, 1, 0],
42//!     [3, 2, 0],
43//!     // Back
44//!     [4, 5, 6],
45//!     [4, 6, 7],
46//!     // Right
47//!     [7, 6, 2],
48//!     [2, 3, 7],
49//!     // Left
50//!     [0, 1, 5],
51//!     [0, 5, 4],
52//!     // Top
53//!     [5, 1, 2],
54//!     [5, 2, 6],
55//!     // Bottom
56//!     [3, 0, 4],
57//!     [7, 3, 4],
58//! ];
59//!
60//! // Step 1. Generate second texture coordinates first.
61//! let patch = uvgen::generate_uvs(
62//!     vertices.iter().map(|v| v.position),
63//!     triangles.iter().cloned(),
64//!     0.005,
65//! )
66//! .unwrap();
67//!
68//! // Step 2. Clone vertices on seams.
69//! for &vertex_index in &patch.additional_vertices {
70//!     let vertex = vertices[vertex_index as usize];
71//!     vertices.push(vertex);
72//! }
73//!
74//! // Step 3. Assign generated second texture coordinates.
75//! for (vertex, tex_coord) in vertices.iter_mut().zip(&patch.second_tex_coords) {
76//!     vertex.tex_coord = *tex_coord;
77//! }
78//!
79//! // Step 4. Replace topology of the mesh using new one, that was produced when generating UVs.
80//! triangles = patch.triangles;
81//!
82//! // Step 5. Generate light map.
83//! let lights = [LightDefinition::Point(PointLightDefinition {
84//!     intensity: 1.0,
85//!     color: Vector3::new(1.0, 1.0, 1.0),
86//!     radius: 2.0,
87//!     position: Vector3::new(0.0, 2.0, 0.0),
88//!     sqr_radius: 4.0,
89//! })];
90//!
91//! let mut mesh = Mesh::new(
92//!     vertices
93//!         .iter()
94//!         .map(|v| WorldVertex {
95//!             world_normal: Default::default(),
96//!             world_position: v.position,
97//!             second_tex_coord: v.tex_coord,
98//!         })
99//!         .collect(),
100//!     triangles,
101//! )
102//! .unwrap();
103//!
104//! // Calculate normals.
105//! for triangle in mesh.triangles.iter() {
106//!     let pos_a = mesh.vertices[triangle[0] as usize].world_position;
107//!     let pos_b = mesh.vertices[triangle[1] as usize].world_position;
108//!     let pos_c = mesh.vertices[triangle[2] as usize].world_position;
109//!
110//!     let normal = (pos_c - pos_a)
111//!         .cross(&(pos_c - pos_b))
112//!         .try_normalize(f32::EPSILON)
113//!         .unwrap_or_default();
114//!
115//!     mesh.vertices[triangle[0] as usize].world_normal = normal;
116//!     mesh.vertices[triangle[1] as usize].world_normal = normal;
117//!     mesh.vertices[triangle[2] as usize].world_normal = normal;
118//! }
119//!
120//! let meshes = vec![mesh];
121//!
122//! let light_map = LightMap::new(&meshes[0], &meshes, &lights, 64);
123//!
124//! dbg!(light_map);
125//! ```
126
127#![forbid(unsafe_code)]
128
129pub use fyrox_math as math;
130pub use uvgen;
131
132pub mod error;
133pub mod input;
134pub mod light;
135
136use crate::{input::Mesh, light::LightDefinition};
137use arrayvec::ArrayVec;
138use math::{octree::OctreeNode, ray::Ray, Rect};
139use nalgebra::{Vector2, Vector3, Vector4};
140use rayon::prelude::*;
141
142// Computes total area of triangles in surface data and returns size of square in which triangles
143// can fit.
144fn estimate_size(data: &Mesh, texels_per_unit: usize) -> usize {
145    let mut area = 0.0;
146    for triangle in data.triangles.iter() {
147        let a = data.vertices[triangle[0] as usize].world_position;
148        let b = data.vertices[triangle[1] as usize].world_position;
149        let c = data.vertices[triangle[2] as usize].world_position;
150        area += math::triangle_area(a, b, c);
151    }
152    area.sqrt().ceil() as usize * texels_per_unit
153}
154
155// Calculates distance attenuation for a point using given distance to the point and radius of a
156// light.
157fn distance_attenuation(distance: f32, sqr_radius: f32) -> f32 {
158    let attenuation = (1.0 - distance * distance / sqr_radius).clamp(0.0, 1.0);
159    attenuation * attenuation
160}
161
162// Calculates properties of pixel (world position, normal) at given position.
163fn pick(
164    uv: Vector2<f32>,
165    grid: &Grid,
166    data: &Mesh,
167    scale: f32,
168) -> Option<(Vector3<f32>, Vector3<f32>)> {
169    if let Some(cell) = grid.pick(uv) {
170        for triangle in cell.triangles.iter().map(|&ti| &data.triangles[ti]) {
171            let ia = triangle[0] as usize;
172            let ib = triangle[1] as usize;
173            let ic = triangle[2] as usize;
174
175            let uv_a = data.vertices[ia].second_tex_coord;
176            let uv_b = data.vertices[ib].second_tex_coord;
177            let uv_c = data.vertices[ic].second_tex_coord;
178
179            let center = (uv_a + uv_b + uv_c).scale(1.0 / 3.0);
180            let to_center = (center - uv)
181                .try_normalize(std::f32::EPSILON)
182                .unwrap_or_default()
183                .scale(scale * 0.3333333);
184
185            let mut current_uv = uv;
186            for _ in 0..3 {
187                let barycentric = math::get_barycentric_coords_2d(current_uv, uv_a, uv_b, uv_c);
188
189                if math::barycentric_is_inside(barycentric) {
190                    let a = data.vertices[ia].world_position;
191                    let b = data.vertices[ib].world_position;
192                    let c = data.vertices[ic].world_position;
193
194                    let na = data.vertices[ia].world_normal;
195                    let nb = data.vertices[ib].world_normal;
196                    let nc = data.vertices[ic].world_normal;
197
198                    return Some((
199                        math::barycentric_to_world(barycentric, a, b, c),
200                        math::barycentric_to_world(barycentric, na, nb, nc),
201                    ));
202                }
203
204                // Offset uv to center for conservative rasterization.
205                current_uv += to_center;
206            }
207        }
208    }
209    None
210}
211
212struct GridCell {
213    // List of triangle indices.
214    triangles: Vec<usize>,
215}
216
217struct Grid {
218    cells: Vec<GridCell>,
219    size: usize,
220    fsize: f32,
221}
222
223impl Grid {
224    // Creates uniform grid where each cell contains list of triangles whose second texture
225    // coordinates intersects with it.
226    fn new(data: &Mesh, size: usize) -> Self {
227        let mut cells = Vec::with_capacity(size);
228        let fsize = size as f32;
229        for y in 0..size {
230            for x in 0..size {
231                let bounds =
232                    Rect::new(x as f32 / fsize, y as f32 / fsize, 1.0 / fsize, 1.0 / fsize);
233
234                let mut triangles = Vec::new();
235
236                for (triangle_index, triangle) in data.triangles.iter().enumerate() {
237                    let uv_a = data.vertices[triangle[0] as usize].second_tex_coord;
238                    let uv_b = data.vertices[triangle[1] as usize].second_tex_coord;
239                    let uv_c = data.vertices[triangle[2] as usize].second_tex_coord;
240                    let uv_min = uv_a.inf(&uv_b).inf(&uv_c);
241                    let uv_max = uv_a.sup(&uv_b).sup(&uv_c);
242                    let triangle_bounds =
243                        Rect::new(uv_min.x, uv_min.y, uv_max.x - uv_min.x, uv_max.y - uv_min.y);
244                    if triangle_bounds.intersects(bounds) {
245                        triangles.push(triangle_index);
246                    }
247                }
248
249                cells.push(GridCell { triangles })
250            }
251        }
252
253        Self {
254            cells,
255            size,
256            fsize: size as f32,
257        }
258    }
259
260    fn pick(&self, v: Vector2<f32>) -> Option<&GridCell> {
261        let ix = (v.x * self.fsize) as usize;
262        let iy = (v.y * self.fsize) as usize;
263        self.cells.get(iy * self.size + ix)
264    }
265}
266
267// https://en.wikipedia.org/wiki/Lambert%27s_cosine_law
268fn lambertian(light_vec: Vector3<f32>, normal: Vector3<f32>) -> f32 {
269    normal.dot(&light_vec).max(0.0)
270}
271
272// https://en.wikipedia.org/wiki/Smoothstep
273fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
274    let k = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
275    k * k * (3.0 - 2.0 * k)
276}
277
278#[derive(Clone, Debug)]
279pub struct LightMap {
280    pub pixels: Vec<u8>,
281    pub width: usize,
282    pub height: usize,
283}
284
285impl LightMap {
286    pub fn new(
287        mesh: &Mesh,
288        other_meshes: &[Mesh],
289        lights: &[LightDefinition],
290        texels_per_unit: usize,
291    ) -> Self {
292        // We have to re-generate new set of world-space vertices because UV generator
293        // may add new vertices on seams.
294        let atlas_size = estimate_size(mesh, texels_per_unit);
295        let scale = 1.0 / atlas_size as f32;
296        let grid = Grid::new(mesh, (atlas_size / 32).max(4));
297
298        let mut pixels: Vec<Vector4<u8>> = vec![Vector4::new(0, 0, 0, 0); atlas_size * atlas_size];
299
300        let half_pixel = scale * 0.5;
301        pixels
302            .par_iter_mut()
303            .enumerate()
304            .for_each(|(i, pixel): (usize, &mut Vector4<u8>)| {
305                let x = i % atlas_size;
306                let y = i / atlas_size;
307
308                let uv = Vector2::new(x as f32 * scale + half_pixel, y as f32 * scale + half_pixel);
309
310                if let Some((world_position, world_normal)) = pick(uv, &grid, mesh, scale) {
311                    let mut pixel_color = Vector3::default();
312                    for light in lights {
313                        let (light_color, mut attenuation, light_position) = match light {
314                            LightDefinition::Directional(directional) => {
315                                let attenuation = directional.intensity
316                                    * lambertian(directional.direction, world_normal);
317                                (directional.color, attenuation, Vector3::default())
318                            }
319                            LightDefinition::Spot(spot) => {
320                                let d = spot.position - world_position;
321                                let distance = d.norm();
322                                let light_vec = d.scale(1.0 / distance);
323                                let spot_angle_cos = light_vec.dot(&spot.direction);
324                                let cone_factor =
325                                    smoothstep(spot.edge0, spot.edge1, spot_angle_cos);
326                                let attenuation = cone_factor
327                                    * spot.intensity
328                                    * lambertian(light_vec, world_normal)
329                                    * distance_attenuation(distance, spot.sqr_distance);
330                                (spot.color, attenuation, spot.position)
331                            }
332                            LightDefinition::Point(point) => {
333                                let d = point.position - world_position;
334                                let distance = d.norm();
335                                let light_vec = d.scale(1.0 / distance);
336                                let attenuation = point.intensity
337                                    * lambertian(light_vec, world_normal)
338                                    * distance_attenuation(distance, point.sqr_radius);
339                                (point.color, attenuation, point.position)
340                            }
341                        };
342                        // Shadows
343                        if attenuation >= 0.01 {
344                            let mut query_buffer = ArrayVec::<usize, 64>::new();
345                            let shadow_bias = 0.01;
346                            let ray = Ray::from_two_points(light_position, world_position);
347                            'outer_loop: for other_instance in other_meshes {
348                                other_instance
349                                    .octree
350                                    .ray_query_static(&ray, &mut query_buffer);
351                                for &node in query_buffer.iter() {
352                                    match other_instance.octree.node(node) {
353                                        OctreeNode::Leaf { indices, .. } => {
354                                            let other_data = other_instance;
355                                            for &triangle_index in indices {
356                                                let triangle =
357                                                    &other_data.triangles[triangle_index as usize];
358                                                let va = other_data.vertices[triangle[0] as usize]
359                                                    .world_position;
360                                                let vb = other_data.vertices[triangle[1] as usize]
361                                                    .world_position;
362                                                let vc = other_data.vertices[triangle[2] as usize]
363                                                    .world_position;
364                                                if let Some(pt) =
365                                                    ray.triangle_intersection_point(&[va, vb, vc])
366                                                {
367                                                    if ray.origin.metric_distance(&pt) + shadow_bias
368                                                        < ray.dir.norm()
369                                                    {
370                                                        attenuation = 0.0;
371                                                        break 'outer_loop;
372                                                    }
373                                                }
374                                            }
375                                        }
376                                        OctreeNode::Branch { .. } => unreachable!(),
377                                    }
378                                }
379                            }
380                        }
381                        pixel_color += light_color.scale(attenuation);
382                    }
383
384                    *pixel = Vector4::new(
385                        (pixel_color.x.clamp(0.0, 1.0) * 255.0) as u8,
386                        (pixel_color.y.clamp(0.0, 1.0) * 255.0) as u8,
387                        (pixel_color.z.clamp(0.0, 1.0) * 255.0) as u8,
388                        255, // Indicates that this pixel was "filled"
389                    );
390                }
391            });
392
393        // Prepare light map for bilinear filtration. This step is mandatory to prevent bleeding.
394        let mut rgb_pixels: Vec<Vector3<u8>> =
395            Vec::with_capacity((atlas_size * atlas_size) as usize);
396        for y in 0..(atlas_size as i32) {
397            for x in 0..(atlas_size as i32) {
398                let fetch = |dx: i32, dy: i32| -> Option<Vector3<u8>> {
399                    pixels
400                        .get(((y + dy) * (atlas_size as i32) + x + dx) as usize)
401                        .and_then(|p| {
402                            if p.w != 0 {
403                                Some(Vector3::new(p.x, p.y, p.z))
404                            } else {
405                                None
406                            }
407                        })
408                };
409
410                let src_pixel = pixels[(y * (atlas_size as i32) + x) as usize];
411                if src_pixel.w == 0 {
412                    // Check neighbour pixels marked as "filled" and use it as value.
413                    if let Some(west) = fetch(-1, 0) {
414                        rgb_pixels.push(west);
415                    } else if let Some(east) = fetch(1, 0) {
416                        rgb_pixels.push(east);
417                    } else if let Some(north) = fetch(0, -1) {
418                        rgb_pixels.push(north);
419                    } else if let Some(south) = fetch(0, 1) {
420                        rgb_pixels.push(south);
421                    } else if let Some(north_west) = fetch(-1, -1) {
422                        rgb_pixels.push(north_west);
423                    } else if let Some(north_east) = fetch(1, -1) {
424                        rgb_pixels.push(north_east);
425                    } else if let Some(south_east) = fetch(1, 1) {
426                        rgb_pixels.push(south_east);
427                    } else if let Some(south_west) = fetch(-1, 1) {
428                        rgb_pixels.push(south_west);
429                    } else {
430                        rgb_pixels.push(Vector3::new(0, 0, 0));
431                    }
432                } else {
433                    rgb_pixels.push(Vector3::new(src_pixel.x, src_pixel.y, src_pixel.z))
434                }
435            }
436        }
437
438        // Blur lightmap using simplest box filter.
439        let mut bytes = Vec::with_capacity((atlas_size * atlas_size * 3) as usize);
440        for y in 0..(atlas_size as i32) {
441            for x in 0..(atlas_size as i32) {
442                if x < 1 || y < 1 || x + 1 == atlas_size as i32 || y + 1 == atlas_size as i32 {
443                    bytes.extend_from_slice(
444                        rgb_pixels[(y * (atlas_size as i32) + x) as usize].as_slice(),
445                    );
446                } else {
447                    let fetch = |dx: i32, dy: i32| -> Vector3<i16> {
448                        let u8_pixel =
449                            rgb_pixels[((y + dy) * (atlas_size as i32) + x + dx) as usize];
450                        Vector3::new(u8_pixel.x as i16, u8_pixel.y as i16, u8_pixel.z as i16)
451                    };
452
453                    let north_west = fetch(-1, -1);
454                    let north = fetch(0, -1);
455                    let north_east = fetch(1, -1);
456                    let west = fetch(-1, 0);
457                    let center = fetch(0, 0);
458                    let east = fetch(1, 0);
459                    let south_west = fetch(-1, 1);
460                    let south = fetch(0, 1);
461                    let south_east = fetch(-1, 1);
462
463                    let sum = north_west
464                        + north
465                        + north_east
466                        + west
467                        + center
468                        + east
469                        + south_west
470                        + south
471                        + south_east;
472
473                    bytes.push((sum.x / 9).clamp(0, 255) as u8);
474                    bytes.push((sum.y / 9).clamp(0, 255) as u8);
475                    bytes.push((sum.z / 9).clamp(0, 255) as u8);
476                }
477            }
478        }
479
480        Self {
481            pixels: bytes,
482            width: atlas_size,
483            height: atlas_size,
484        }
485    }
486}