1#![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
73pub 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, 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#[derive(Default, Clone, Debug, Visit, Reflect)]
123pub struct LightmapEntry {
124 pub texture: Option<TextureResource>,
130 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#[derive(Default, Clone, Debug, Visit, Reflect)]
164pub struct Lightmap {
165 pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
168
169 #[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#[derive(Clone, Default)]
185pub struct CancellationToken(pub Arc<AtomicBool>);
186
187impl CancellationToken {
188 pub fn new() -> Self {
190 Self::default()
191 }
192
193 pub fn is_cancelled(&self) -> bool {
195 self.0.load(atomic::Ordering::SeqCst)
196 }
197
198 pub fn cancel(&self) {
200 self.0.store(true, atomic::Ordering::SeqCst)
201 }
202}
203
204#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
206#[repr(u32)]
207pub enum ProgressStage {
208 LightsCaching = 0,
210 UvGeneration = 1,
212 GeometryCaching = 2,
214 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#[derive(Default)]
239pub struct ProgressData {
240 stage: AtomicU32,
241 progress: AtomicU32,
243 max_iterations: AtomicU32,
244}
245
246impl ProgressData {
247 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 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 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 fn advance_progress(&self) {
278 self.progress.fetch_add(1, atomic::Ordering::SeqCst);
279 }
280}
281
282#[derive(Clone, Default)]
284pub struct ProgressIndicator(pub Arc<ProgressData>);
285
286impl ProgressIndicator {
287 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#[derive(Debug)]
303pub enum LightmapGenerationError {
304 Cancelled,
306 InvalidIndex,
308 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
334pub struct LightmapInputData {
338 data_set: FxHashMap<u64, SurfaceResource>,
339 instances: Vec<Instance>,
340 lights: FxHashMap<Handle<Node>, LightDefinition>,
341}
342
343impl LightmapInputData {
344 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 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 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 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 data: None,
475 });
476 }
477 }
478 }
479
480 Ok(Self {
481 data_set,
482 instances,
483 lights,
484 })
485 }
486}
487
488impl Lightmap {
489 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 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 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 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
675fn 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}