1#![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
74pub 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#[derive(Default, Clone, Debug, Visit, Reflect)]
128pub struct LightmapEntry {
129 pub texture: Option<TextureResource>,
135 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#[derive(Clone, Debug, Visit, Reflect)]
169pub struct Lightmap {
170 #[visit(optional)] pub texture_name: String,
174
175 #[visit(optional)] pub second_tex_coord_location: u8,
179
180 pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
183
184 #[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#[derive(Clone, Default)]
211pub struct CancellationToken(pub Arc<AtomicBool>);
212
213impl CancellationToken {
214 pub fn new() -> Self {
216 Self::default()
217 }
218
219 pub fn is_cancelled(&self) -> bool {
221 self.0.load(atomic::Ordering::SeqCst)
222 }
223
224 pub fn cancel(&self) {
226 self.0.store(true, atomic::Ordering::SeqCst)
227 }
228}
229
230#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
232#[repr(u32)]
233pub enum ProgressStage {
234 LightsCaching = 0,
236 UvGeneration = 1,
238 GeometryCaching = 2,
240 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#[derive(Default)]
265pub struct ProgressData {
266 stage: AtomicU32,
267 progress: AtomicU32,
269 max_iterations: AtomicU32,
270}
271
272impl ProgressData {
273 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 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 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 fn advance_progress(&self) {
304 self.progress.fetch_add(1, atomic::Ordering::SeqCst);
305 }
306}
307
308#[derive(Clone, Default)]
310pub struct ProgressIndicator(pub Arc<ProgressData>);
311
312impl ProgressIndicator {
313 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#[derive(Debug)]
329pub enum LightmapGenerationError {
330 Cancelled,
332 InvalidIndex,
334 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
362pub 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 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 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 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 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 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 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 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 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 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
756fn 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}