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 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
71pub 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, 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#[derive(Default, Clone, Debug, Visit, Reflect)]
121pub struct LightmapEntry {
122 pub texture: Option<TextureResource>,
128 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#[derive(Default, Clone, Debug, Visit, Reflect)]
162pub struct Lightmap {
163 pub map: FxHashMap<Handle<Node>, Vec<LightmapEntry>>,
166
167 #[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#[derive(Clone, Default)]
183pub struct CancellationToken(pub Arc<AtomicBool>);
184
185impl CancellationToken {
186 pub fn new() -> Self {
188 Self::default()
189 }
190
191 pub fn is_cancelled(&self) -> bool {
193 self.0.load(atomic::Ordering::SeqCst)
194 }
195
196 pub fn cancel(&self) {
198 self.0.store(true, atomic::Ordering::SeqCst)
199 }
200}
201
202#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
204#[repr(u32)]
205pub enum ProgressStage {
206 LightsCaching = 0,
208 UvGeneration = 1,
210 GeometryCaching = 2,
212 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#[derive(Default)]
237pub struct ProgressData {
238 stage: AtomicU32,
239 progress: AtomicU32,
241 max_iterations: AtomicU32,
242}
243
244impl ProgressData {
245 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 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 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 fn advance_progress(&self) {
276 self.progress.fetch_add(1, atomic::Ordering::SeqCst);
277 }
278}
279
280#[derive(Clone, Default)]
282pub struct ProgressIndicator(pub Arc<ProgressData>);
283
284impl ProgressIndicator {
285 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#[derive(Debug)]
301pub enum LightmapGenerationError {
302 Cancelled,
304 InvalidIndex,
306 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
332pub struct LightmapInputData {
336 data_set: FxHashMap<u64, SurfaceResource>,
337 instances: Vec<Instance>,
338 lights: FxHashMap<Handle<Node>, LightDefinition>,
339}
340
341impl LightmapInputData {
342 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 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 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 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 data: None,
473 });
474 }
475 }
476 }
477
478 Ok(Self {
479 data_set,
480 instances,
481 lights,
482 })
483 }
484}
485
486impl Lightmap {
487 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 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 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 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
670fn 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}