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