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