Skip to main content

fyrox_impl/utils/
lightmap.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Module to generate lightmaps for surfaces.
22//!
23//! # Performance
24//!
25//! This is CPU lightmapper, its performance is linear with core count of your CPU.
26
27#![forbid(unsafe_code)]
28
29use crate::{
30    asset::manager::{ResourceManager, ResourceRegistrationError},
31    core::{
32        algebra::{Matrix3, Matrix4, Point3, Vector2, Vector3},
33        math::{Matrix4Ext, TriangleDefinition},
34        pool::Handle,
35        reflect::prelude::*,
36        visitor::{prelude::*, BinaryBlob},
37    },
38    graph::SceneGraph,
39    resource::texture::{Texture, TextureKind, TexturePixelKind, TextureResource},
40    scene::{
41        light::{directional::DirectionalLight, point::PointLight, spot::SpotLight},
42        mesh::{
43            buffer::{
44                VertexAttributeDataType, VertexAttributeDescriptor, VertexAttributeUsage,
45                VertexFetchError, VertexReadTrait, VertexWriteTrait,
46            },
47            surface::{SurfaceData, SurfaceResource},
48            Mesh,
49        },
50        node::Node,
51        Scene,
52    },
53    utils::{uvgen, uvgen::SurfaceDataPatch},
54};
55use fxhash::FxHashMap;
56use fyrox_core::{ok_or_continue, warn, Uuid};
57use fyrox_graph::SceneGraphNode;
58use fyrox_material::Material;
59use fyrox_resource::ResourceData;
60use lightmap::light::{
61    DirectionalLightDefinition, LightDefinition, PointLightDefinition, SpotLightDefinition,
62};
63use rayon::prelude::*;
64use std::{
65    fmt::{Display, Formatter},
66    ops::Deref,
67    path::Path,
68    sync::{
69        atomic::{self, AtomicBool, AtomicU32},
70        Arc,
71    },
72};
73
74/// Applies surface data patch to a surface data.
75pub fn apply_surface_data_patch(
76    data: &mut SurfaceData,
77    patch: &SurfaceDataPatch,
78    tex_coord_binding_point: u8,
79) {
80    if !data
81        .vertex_buffer
82        .has_attribute(VertexAttributeUsage::TexCoord1)
83    {
84        data.vertex_buffer
85            .modify()
86            .add_attribute(
87                VertexAttributeDescriptor {
88                    usage: VertexAttributeUsage::TexCoord1,
89                    data_type: VertexAttributeDataType::F32,
90                    size: 2,
91                    divisor: 0,
92                    shader_location: tex_coord_binding_point,
93                    normalized: false,
94                },
95                Vector2::<f32>::default(),
96            )
97            .unwrap();
98    }
99
100    data.geometry_buffer.set_triangles(
101        patch
102            .triangles
103            .iter()
104            .map(|t| TriangleDefinition(*t))
105            .collect::<Vec<_>>(),
106    );
107
108    let mut vertex_buffer_mut = data.vertex_buffer.modify();
109    for &v in patch.additional_vertices.iter() {
110        vertex_buffer_mut.duplicate(v as usize);
111    }
112
113    assert_eq!(
114        vertex_buffer_mut.vertex_count() as usize,
115        patch.second_tex_coords.len()
116    );
117    for (mut view, &tex_coord) in vertex_buffer_mut
118        .iter_mut()
119        .zip(patch.second_tex_coords.iter())
120    {
121        view.write_2_f32(VertexAttributeUsage::TexCoord1, tex_coord)
122            .unwrap();
123    }
124}
125
126/// Lightmap entry.
127#[derive(Default, Clone, Debug, Visit, Reflect)]
128pub struct LightmapEntry {
129    /// Lightmap texture.
130    ///
131    /// TODO: Is single texture enough? There may be surfaces with huge amount of faces
132    ///  which may not fit into texture, because there is hardware limit on most GPUs
133    ///  up to 8192x8192 pixels.
134    pub texture: Option<TextureResource>,
135    /// List of lights that were used to generate this lightmap. This list is used for
136    /// masking when applying dynamic lights for surfaces with light, it prevents double
137    /// lighting.
138    pub lights: Vec<Handle<Node>>,
139}
140
141#[doc(hidden)]
142#[derive(Default, Debug, Clone)]
143pub struct SurfaceDataPatchWrapper(pub SurfaceDataPatch);
144
145impl Visit for SurfaceDataPatchWrapper {
146    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
147        let mut region = visitor.enter_region(name)?;
148
149        self.0.data_id.visit("DataId", &mut region)?;
150        BinaryBlob {
151            vec: &mut self.0.triangles,
152        }
153        .visit("Triangles", &mut region)?;
154        BinaryBlob {
155            vec: &mut self.0.second_tex_coords,
156        }
157        .visit("SecondTexCoords", &mut region)?;
158        BinaryBlob {
159            vec: &mut self.0.additional_vertices,
160        }
161        .visit("AdditionalVertices", &mut region)?;
162
163        Ok(())
164    }
165}
166
167/// Lightmap is a texture with precomputed lighting.
168#[derive(Clone, Debug, Visit, Reflect)]
169pub struct Lightmap {
170    /// Name of the property this light map is bound to in all materials across all
171    /// surfaces for which the light map was generated.
172    #[visit(optional)] // Backward compatibility
173    pub texture_name: String,
174
175    /// Location of the second texture coordinate attribute in the vertex buffer
176    /// of all surfaces.
177    #[visit(optional)] // Backward compatibility
178    pub second_tex_coord_location: u8,
179
180    /// Node handle to lightmap mapping. It is used to quickly get information about
181    /// lightmaps for any node in scene.
182    pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
183
184    /// List of surface data patches. Each patch will be applied to corresponding
185    /// surface data on resolve stage.
186    // We don't need to inspect patches, because they contain no useful data.
187    #[reflect(hidden)]
188    pub patches: FxHashMap<u64, SurfaceDataPatchWrapper>,
189}
190
191impl Default for Lightmap {
192    fn default() -> Self {
193        Self {
194            texture_name: "lightmapTexture".to_string(),
195            second_tex_coord_location: 6,
196            map: Default::default(),
197            patches: Default::default(),
198        }
199    }
200}
201
202struct Instance {
203    owner: Handle<Node>,
204    source_data: SurfaceResource,
205    data: Option<lightmap::input::Mesh>,
206    transform: Matrix4<f32>,
207}
208
209/// Small helper that allows you stop lightmap generation in any time.
210#[derive(Clone, Default)]
211pub struct CancellationToken(pub Arc<AtomicBool>);
212
213impl CancellationToken {
214    /// Creates new cancellation token.
215    pub fn new() -> Self {
216        Self::default()
217    }
218
219    /// Checks if generation was cancelled.
220    pub fn is_cancelled(&self) -> bool {
221        self.0.load(atomic::Ordering::SeqCst)
222    }
223
224    /// Raises cancellation flag, actual cancellation is not immediate!
225    pub fn cancel(&self) {
226        self.0.store(true, atomic::Ordering::SeqCst)
227    }
228}
229
230/// Lightmap generation stage.
231#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
232#[repr(u32)]
233pub enum ProgressStage {
234    /// Gathering info about lights, doing precalculations.
235    LightsCaching = 0,
236    /// Generating secondary texture coordinates.
237    UvGeneration = 1,
238    /// Caching geometry, building octrees.
239    GeometryCaching = 2,
240    /// Actual lightmap generation.
241    CalculatingLight = 3,
242}
243
244impl Display for ProgressStage {
245    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
246        match self {
247            ProgressStage::LightsCaching => {
248                write!(f, "Caching Lights")
249            }
250            ProgressStage::UvGeneration => {
251                write!(f, "Generating UVs")
252            }
253            ProgressStage::GeometryCaching => {
254                write!(f, "Caching Geometry")
255            }
256            ProgressStage::CalculatingLight => {
257                write!(f, "Calculating Light")
258            }
259        }
260    }
261}
262
263/// Progress internals.
264#[derive(Default)]
265pub struct ProgressData {
266    stage: AtomicU32,
267    // Range is [0; max_iterations]
268    progress: AtomicU32,
269    max_iterations: AtomicU32,
270}
271
272impl ProgressData {
273    /// Returns progress percentage in [0; 100] range.
274    pub fn progress_percent(&self) -> u32 {
275        let iterations = self.max_iterations.load(atomic::Ordering::SeqCst);
276        if iterations > 0 {
277            self.progress.load(atomic::Ordering::SeqCst) * 100 / iterations
278        } else {
279            0
280        }
281    }
282
283    /// Returns current stage.
284    pub fn stage(&self) -> ProgressStage {
285        match self.stage.load(atomic::Ordering::SeqCst) {
286            0 => ProgressStage::LightsCaching,
287            1 => ProgressStage::UvGeneration,
288            2 => ProgressStage::GeometryCaching,
289            3 => ProgressStage::CalculatingLight,
290            _ => unreachable!(),
291        }
292    }
293
294    /// Sets new stage with max iterations per stage.
295    fn set_stage(&self, stage: ProgressStage, max_iterations: u32) {
296        self.max_iterations
297            .store(max_iterations, atomic::Ordering::SeqCst);
298        self.progress.store(0, atomic::Ordering::SeqCst);
299        self.stage.store(stage as u32, atomic::Ordering::SeqCst);
300    }
301
302    /// Advances progress.
303    fn advance_progress(&self) {
304        self.progress.fetch_add(1, atomic::Ordering::SeqCst);
305    }
306}
307
308/// Small helper that allows you to track progress of lightmap generation.
309#[derive(Clone, Default)]
310pub struct ProgressIndicator(pub Arc<ProgressData>);
311
312impl ProgressIndicator {
313    /// Creates new progress indicator.
314    pub fn new() -> Self {
315        Self::default()
316    }
317}
318
319impl Deref for ProgressIndicator {
320    type Target = ProgressData;
321
322    fn deref(&self) -> &Self::Target {
323        &self.0
324    }
325}
326
327/// An error that may occur during ligthmap generation.
328#[derive(Debug)]
329pub enum LightmapGenerationError {
330    /// Generation was cancelled by user.
331    Cancelled,
332    /// An index of a vertex in a triangle is out of bounds.
333    InvalidIndex,
334    /// Vertex buffer of a mesh lacks required data.
335    InvalidData(VertexFetchError),
336}
337
338impl std::error::Error for LightmapGenerationError {}
339
340impl Display for LightmapGenerationError {
341    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
342        match self {
343            LightmapGenerationError::Cancelled => {
344                write!(f, "Lightmap generation was cancelled by the user.")
345            }
346            LightmapGenerationError::InvalidIndex => {
347                write!(f, "An index of a vertex in a triangle is out of bounds.")
348            }
349            LightmapGenerationError::InvalidData(v) => {
350                write!(f, "Vertex buffer of a mesh lacks required data {v}.")
351            }
352        }
353    }
354}
355
356impl From<VertexFetchError> for LightmapGenerationError {
357    fn from(e: VertexFetchError) -> Self {
358        Self::InvalidData(e)
359    }
360}
361
362/// Data set required to generate a lightmap. It could be produced from a scene using [`LightmapInputData::from_scene`] method.
363/// It is used to split preparation step from the actual lightmap generation; to be able to put heavy generation in a separate
364/// thread.
365pub struct LightmapInputData {
366    texture_name: String,
367    second_tex_coord_location: u8,
368    data_set: FxHashMap<u64, SurfaceResource>,
369    instances: Vec<Instance>,
370    lights: FxHashMap<Handle<Node>, LightDefinition>,
371}
372
373fn has_lightmap_texture_binding_point(
374    texture_name: &str,
375    node: &Node,
376    surface_index: usize,
377    material: &Material,
378) -> bool {
379    if let Some(shader) = material.shader().data_ref().as_loaded_ref() {
380        if shader.has_texture_resource(texture_name) {
381            true
382        } else {
383            warn!(
384                "[Lightmap]: skipping {}({}) node's surface {surface_index}, because its material \
385                does not have a {texture_name} texture resource!",
386                node.name(),
387                node.self_handle()
388            );
389            false
390        }
391    } else {
392        warn!(
393            "[Lightmap]: skipping {}({}) node's surface {surface_index}, because its material \
394            has unloaded shader!",
395            node.name(),
396            node.self_handle()
397        );
398        false
399    }
400}
401
402impl LightmapInputData {
403    /// Creates a new input data that can be later used to generate a lightmap.
404    pub fn from_scene<F>(
405        texture_name: &str,
406        second_tex_coord_location: u8,
407        scene: &Scene,
408        mut filter: F,
409        cancellation_token: CancellationToken,
410        progress_indicator: ProgressIndicator,
411    ) -> Result<Self, LightmapGenerationError>
412    where
413        F: FnMut(Handle<Node>, &Node) -> bool,
414    {
415        // Extract info about lights first. We need it to be in separate array because
416        // it won't be possible to store immutable references to light sources and at the
417        // same time modify meshes. Also it precomputes a lot of things for faster calculations.
418        let mut light_count = 0;
419        for (handle, node) in scene.graph.pair_iter() {
420            if filter(handle, node)
421                && (node.cast::<PointLight>().is_some()
422                    || node.cast::<SpotLight>().is_some()
423                    || node.cast::<DirectionalLight>().is_some())
424            {
425                light_count += 1;
426            }
427        }
428
429        progress_indicator.set_stage(ProgressStage::LightsCaching, light_count);
430
431        let mut lights = FxHashMap::default();
432
433        for (handle, node) in scene.graph.pair_iter() {
434            if !filter(handle, node) {
435                continue;
436            }
437
438            if cancellation_token.is_cancelled() {
439                return Err(LightmapGenerationError::Cancelled);
440            }
441
442            if !node.is_globally_enabled() {
443                continue;
444            }
445
446            if let Some(point) = node.cast::<PointLight>() {
447                lights.insert(
448                    handle,
449                    LightDefinition::Point(PointLightDefinition {
450                        intensity: point.base_light_ref().intensity(),
451                        position: node.global_position(),
452                        color: point.base_light_ref().color().srgb_to_linear().as_frgb(),
453                        radius: point.radius(),
454                        sqr_radius: point.radius() * point.radius(),
455                    }),
456                )
457            } else if let Some(spot) = node.cast::<SpotLight>() {
458                lights.insert(
459                    handle,
460                    LightDefinition::Spot(SpotLightDefinition {
461                        intensity: spot.base_light_ref().intensity(),
462                        edge0: ((spot.hotspot_cone_angle() + spot.falloff_angle_delta()) * 0.5)
463                            .cos(),
464                        edge1: (spot.hotspot_cone_angle() * 0.5).cos(),
465                        color: spot.base_light_ref().color().srgb_to_linear().as_frgb(),
466                        direction: node
467                            .up_vector()
468                            .try_normalize(f32::EPSILON)
469                            .unwrap_or_else(Vector3::y),
470                        position: node.global_position(),
471                        distance: spot.distance(),
472                        sqr_distance: spot.distance() * spot.distance(),
473                    }),
474                )
475            } else if let Some(directional) = node.cast::<DirectionalLight>() {
476                lights.insert(
477                    handle,
478                    LightDefinition::Directional(DirectionalLightDefinition {
479                        intensity: directional.base_light_ref().intensity(),
480                        direction: node
481                            .up_vector()
482                            .try_normalize(f32::EPSILON)
483                            .unwrap_or_else(Vector3::y),
484                        color: directional
485                            .base_light_ref()
486                            .color()
487                            .srgb_to_linear()
488                            .as_frgb(),
489                    }),
490                )
491            } else {
492                continue;
493            };
494
495            progress_indicator.advance_progress()
496        }
497
498        let mut instances = Vec::new();
499        let mut data_set = FxHashMap::default();
500
501        let mut stack = vec![scene.graph.root()];
502
503        'node_loop: while let Some(handle) = stack.pop() {
504            let node = ok_or_continue!(scene.graph.try_get_node(handle));
505
506            if !filter(handle, node) {
507                continue 'node_loop;
508            }
509
510            stack.extend_from_slice(node.children());
511
512            if let Some(mesh) = node.cast::<Mesh>() {
513                if !mesh.global_visibility() || !mesh.is_globally_enabled() {
514                    warn!(
515                        "[Lightmap]: skipping {}({}) node, because \
516                    it is either invisible or disabled.",
517                        mesh.name(),
518                        handle
519                    );
520                    continue;
521                }
522                let global_transform = mesh.global_transform();
523                'surface_loop: for (surface_index, surface) in mesh.surfaces().iter().enumerate() {
524                    // Check material for compatibility.
525                    let mut material_state = surface.material().state();
526                    let Some(material) = material_state.data() else {
527                        continue 'surface_loop;
528                    };
529                    if !has_lightmap_texture_binding_point(
530                        texture_name,
531                        node,
532                        surface_index,
533                        material,
534                    ) {
535                        continue 'surface_loop;
536                    }
537
538                    // Gather unique "list" of surface data to generate UVs for.
539                    let data = surface.data();
540                    data_set.entry(data.key()).or_insert_with(|| surface.data());
541                    instances.push(Instance {
542                        owner: handle,
543                        source_data: data.clone(),
544                        transform: global_transform,
545                        // Calculated down below.
546                        data: None,
547                    });
548                }
549            }
550        }
551
552        Ok(Self {
553            texture_name: texture_name.to_string(),
554            second_tex_coord_location,
555            data_set,
556            instances,
557            lights,
558        })
559    }
560}
561
562impl Lightmap {
563    /// Loads a light map from the given path.
564    pub async fn load<P: AsRef<Path>>(
565        path: P,
566        resource_manager: ResourceManager,
567    ) -> Result<Lightmap, VisitError> {
568        let mut visitor = Visitor::load_from_file(path).await?;
569        visitor.blackboard.register(Arc::new(resource_manager));
570        let mut lightmap = Lightmap::default();
571        lightmap.visit("Lightmap", &mut visitor)?;
572        Ok(lightmap)
573    }
574
575    /// Saves a light map to the given file. Keep in mind, that the textures should be saved separately first, via
576    /// [`Self::save_textures`] method.
577    pub fn save<P: AsRef<Path>>(&mut self, path: P) -> VisitResult {
578        let mut visitor = Visitor::new();
579        self.visit("Lightmap", &mut visitor)?;
580        visitor.save_binary_to_file(path)?;
581        Ok(())
582    }
583
584    /// Generates lightmap for given scene. This method **automatically** generates secondary
585    /// texture coordinates! This method is blocking, however internally it uses massive parallelism
586    /// to use all available CPU power efficiently.
587    ///
588    /// `texels_per_unit` defines resolution of lightmap, the higher value is, the more quality
589    /// lightmap will be generated, but also it will be slow to generate.
590    /// `progress_indicator` allows you to get info about current progress.
591    /// `cancellation_token` allows you to stop generation in any time.
592    pub fn new(
593        data: LightmapInputData,
594        texels_per_unit: u32,
595        uv_spacing: f32,
596        cancellation_token: CancellationToken,
597        progress_indicator: ProgressIndicator,
598    ) -> Result<Self, LightmapGenerationError> {
599        let LightmapInputData {
600            texture_name,
601            second_tex_coord_location,
602            data_set,
603            mut instances,
604            lights,
605        } = data;
606
607        progress_indicator.set_stage(ProgressStage::UvGeneration, data_set.len() as u32);
608
609        let patches = data_set
610            .into_par_iter()
611            .map(|(_, surface)| {
612                if cancellation_token.is_cancelled() {
613                    Err(LightmapGenerationError::Cancelled)
614                } else {
615                    let mut data = surface.data_ref();
616                    let data = &mut *data;
617
618                    let mut patch = uvgen::generate_uvs(
619                        data.vertex_buffer
620                            .iter()
621                            .map(|v| v.read_3_f32(VertexAttributeUsage::Position).unwrap()),
622                        data.geometry_buffer.iter().map(|t| t.0),
623                        uv_spacing,
624                    )
625                    .ok_or(LightmapGenerationError::InvalidIndex)?;
626                    patch.data_id = data.content_hash();
627
628                    apply_surface_data_patch(data, &patch, second_tex_coord_location);
629
630                    progress_indicator.advance_progress();
631                    Ok((patch.data_id, SurfaceDataPatchWrapper(patch)))
632                }
633            })
634            .collect::<Result<FxHashMap<_, _>, LightmapGenerationError>>()?;
635
636        progress_indicator.set_stage(ProgressStage::GeometryCaching, instances.len() as u32);
637
638        instances
639            .par_iter_mut()
640            .map(|instance: &mut Instance| {
641                if cancellation_token.is_cancelled() {
642                    Err(LightmapGenerationError::Cancelled)
643                } else {
644                    let data = instance.source_data.data_ref();
645
646                    let normal_matrix = instance
647                        .transform
648                        .basis()
649                        .try_inverse()
650                        .map(|m| m.transpose())
651                        .unwrap_or_else(Matrix3::identity);
652
653                    let world_vertices = data
654                        .vertex_buffer
655                        .iter()
656                        .map(|view| {
657                            let world_position = instance
658                                .transform
659                                .transform_point(&Point3::from(
660                                    view.read_3_f32(VertexAttributeUsage::Position).unwrap(),
661                                ))
662                                .coords;
663                            let world_normal = (normal_matrix
664                                * view.read_3_f32(VertexAttributeUsage::Normal).unwrap())
665                            .try_normalize(f32::EPSILON)
666                            .unwrap_or_default();
667                            lightmap::input::WorldVertex {
668                                world_normal,
669                                world_position,
670                                second_tex_coord: view
671                                    .read_2_f32(VertexAttributeUsage::TexCoord1)
672                                    .unwrap(),
673                            }
674                        })
675                        .collect::<Vec<_>>();
676
677                    instance.data = Some(
678                        lightmap::input::Mesh::new(
679                            world_vertices,
680                            data.geometry_buffer
681                                .triangles_ref()
682                                .iter()
683                                .map(|t| t.0)
684                                .collect(),
685                        )
686                        .unwrap(),
687                    );
688
689                    progress_indicator.advance_progress();
690
691                    Ok(())
692                }
693            })
694            .collect::<Result<(), LightmapGenerationError>>()?;
695
696        progress_indicator.set_stage(ProgressStage::CalculatingLight, instances.len() as u32);
697
698        let mut map: FxHashMap<Handle<Node>, Vec<LightmapEntry>> = FxHashMap::default();
699        let meshes = instances
700            .iter_mut()
701            .filter_map(|i| i.data.take())
702            .collect::<Vec<_>>();
703        let light_definitions = lights.values().cloned().collect::<Vec<_>>();
704        for (mesh, instance) in meshes.iter().zip(instances.iter()) {
705            if cancellation_token.is_cancelled() {
706                return Err(LightmapGenerationError::Cancelled);
707            }
708
709            let lightmap = generate_lightmap(mesh, &meshes, &light_definitions, texels_per_unit);
710            map.entry(instance.owner).or_default().push(LightmapEntry {
711                texture: Some(TextureResource::new_ok(
712                    Uuid::new_v4(),
713                    Default::default(),
714                    lightmap,
715                )),
716                lights: lights.keys().cloned().collect(),
717            });
718
719            progress_indicator.advance_progress();
720        }
721
722        Ok(Self {
723            texture_name,
724            second_tex_coord_location,
725            map,
726            patches,
727        })
728    }
729
730    /// Saves lightmap textures into specified folder.
731    pub fn save_textures<P: AsRef<Path>>(
732        &self,
733        base_path: P,
734        resource_manager: ResourceManager,
735    ) -> Result<(), ResourceRegistrationError> {
736        if !base_path.as_ref().exists() {
737            std::fs::create_dir_all(base_path.as_ref())
738                .map_err(|_| ResourceRegistrationError::UnableToRegister)?;
739        }
740
741        for (handle, entries) in self.map.iter() {
742            let handle_path = handle.index().to_string();
743            for (i, entry) in entries.iter().enumerate() {
744                let file_path = handle_path.clone() + "_" + i.to_string().as_str() + ".png";
745                let texture = entry.texture.clone().unwrap();
746                let full_path = base_path.as_ref().join(file_path);
747                if texture.data_ref().save(&full_path).is_ok() {
748                    resource_manager.register(texture.into_untyped(), full_path)?;
749                }
750            }
751        }
752        Ok(())
753    }
754}
755
756/// Generates lightmap for given surface data with specified transform.
757///
758/// # Performance
759///
760/// This method is has linear complexity - the more complex mesh you pass, the more
761/// time it will take. Required time increases drastically if you enable shadows and
762/// global illumination (TODO), because in this case your data will be raytraced.
763fn generate_lightmap(
764    mesh: &lightmap::input::Mesh,
765    other_meshes: &[lightmap::input::Mesh],
766    lights: &[LightDefinition],
767    texels_per_unit: u32,
768) -> Texture {
769    let map = lightmap::LightMap::new(mesh, other_meshes, lights, texels_per_unit as usize);
770
771    Texture::from_bytes(
772        TextureKind::Rectangle {
773            width: map.width as u32,
774            height: map.height as u32,
775        },
776        TexturePixelKind::RGB8,
777        map.pixels,
778    )
779    .unwrap()
780}
781
782#[cfg(test)]
783mod test {
784    use crate::{
785        asset::ResourceData,
786        core::algebra::{Matrix4, Vector3},
787        scene::{
788            base::BaseBuilder,
789            light::{point::PointLightBuilder, BaseLightBuilder},
790            mesh::{
791                surface::SurfaceResource,
792                surface::{SurfaceBuilder, SurfaceData},
793                MeshBuilder,
794            },
795            transform::TransformBuilder,
796            Scene,
797        },
798        utils::lightmap::{Lightmap, LightmapInputData},
799    };
800    use fyrox_resource::untyped::ResourceKind;
801    use std::path::Path;
802    use uuid::Uuid;
803
804    #[test]
805    fn test_generate_lightmap() {
806        let mut scene = Scene::new();
807
808        let data = SurfaceData::make_cone(
809            16,
810            1.0,
811            1.0,
812            &Matrix4::new_nonuniform_scaling(&Vector3::new(1.0, 1.1, 1.0)),
813        );
814
815        MeshBuilder::new(BaseBuilder::new())
816            .with_surfaces(vec![SurfaceBuilder::new(SurfaceResource::new_ok(
817                Uuid::new_v4(),
818                ResourceKind::Embedded,
819                data,
820            ))
821            .build()])
822            .build(&mut scene.graph);
823
824        PointLightBuilder::new(BaseLightBuilder::new(
825            BaseBuilder::new().with_local_transform(
826                TransformBuilder::new()
827                    .with_local_position(Vector3::new(0.0, 2.0, 0.0))
828                    .build(),
829            ),
830        ))
831        .with_radius(4.0)
832        .build(&mut scene.graph);
833
834        let data = LightmapInputData::from_scene(
835            "lightmapTexture",
836            6,
837            &scene,
838            |_, _| true,
839            Default::default(),
840            Default::default(),
841        )
842        .unwrap();
843
844        let lightmap =
845            Lightmap::new(data, 64, 0.005, Default::default(), Default::default()).unwrap();
846
847        let mut counter = 0;
848        for entry_set in lightmap.map.values() {
849            for entry in entry_set {
850                let mut data = entry.texture.as_ref().unwrap().data_ref();
851                data.save(Path::new(&format!("{counter}.png"))).unwrap();
852                counter += 1;
853            }
854        }
855    }
856}