1#![allow(clippy::doc_markdown)]
2
3use gloss_hecs::{CommandBuffer, Component, ComponentRef, DynamicBundle, Entity, EntityBuilder, EntityRef, World};
4use log::{error, trace};
5
6use crate::{
7 actor::Actor,
8 builders,
9 camera::Camera,
10 components::{
11 BoundingBox, CamController, ColorsGPU, DiffuseImg, DiffuseTex, EdgesGPU, EnvironmentMapGpu, FacesGPU, ImgConfig, LightEmit, MeshColorType,
12 MetalnessTex, ModelMatrix, Name, NormalTex, NormalsGPU, PosLookat, Projection, ProjectionWithFov, Renderable, RoughnessTex, ShadowCaster,
13 ShadowMap, TangentsGPU, UVsGPU, Verts, VertsGPU, VisLines, VisMesh, VisPoints,
14 },
15 config::{Config, FloorTexture, FloorType, LightConfig},
16 light::Light,
17};
18
19use gloss_geometry::geom;
20use gloss_utils::abi_stable_aliases::std_types::{RHashMap, RString};
21#[cfg(not(target_arch = "wasm32"))]
22use gloss_utils::abi_stable_aliases::StableAbi;
23use gloss_utils::tensor::{DynamicMatrixOps, DynamicTensorOps};
24use nalgebra as na;
25
26pub static GLOSS_FLOOR_NAME: &str = "floor";
27pub static GLOSS_CAM_NAME: &str = "gloss_camera";
28
29static CHECKERBOARD_BYTES: &[u8; 2324] = include_bytes!("../data/checkerboard.png");
31
32#[repr(C)]
34#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
35#[allow(non_upper_case_globals, non_camel_case_types)]
36pub struct Scene {
37 pub world: World,
38 name2entity: RHashMap<RString, Entity>,
39 pub command_buffer: CommandBuffer, entity_resource: Entity, }
42impl Default for Scene {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Scene {
49 pub fn new() -> Self {
50 let mut world = World::default();
51 let name2entity = RHashMap::<RString, Entity>::new();
52 let command_buffer = CommandBuffer::new();
53 let entity_resource = world.spawn((Name("entity_resource".to_string()),));
54 Self {
55 world,
56 name2entity,
57 command_buffer,
58 entity_resource,
59 }
60 }
61
62 pub fn get_entity_resource(&self) -> Entity {
64 self.entity_resource
65 }
66
67 pub fn get_unused_name(&self) -> String {
69 let mut cur_nr = self.get_renderables(false).len();
70 loop {
71 let name = String::from("ent_") + &cur_nr.to_string();
72 let r_name = RString::from(name.clone());
73 if !self.name2entity.contains_key(&r_name) {
74 return name;
75 }
76 cur_nr += 1;
77 }
78 }
79
80 pub fn get_or_create_hidden_entity(&mut self, name: &str) -> EntityMut {
84 let r_name = RString::from(name.to_string());
85 let entity_ref = self
86 .name2entity
87 .entry(r_name)
88 .or_insert_with(|| self.world.spawn((Name(name.to_string()),))); EntityMut::new(&mut self.world, *entity_ref)
90 }
91
92 pub fn get_or_create_entity(&mut self, name: &str) -> EntityMut {
96 let r_name = RString::from(name.to_string());
97 let entity_ref = self
98 .name2entity
99 .entry(r_name)
100 .or_insert_with(|| self.world.spawn((Name(name.to_string()), Renderable))); EntityMut::new(&mut self.world, *entity_ref)
102 }
103
104 pub fn despawn_with_name(&mut self, name: &str) {
106 if let Some(entity) = self.get_entity_with_name(name) {
109 let _ = self.world.despawn(entity);
110 let r_name = RString::from(name.to_string());
111 let _ = self.name2entity.remove(&r_name);
112 }
113 }
114
115 pub fn despawn(&mut self, entity: Entity) {
116 let name = self.get_comp::<&Name>(&entity).map(|x| RString::from(x.0.to_string()));
119 if let Ok(name) = name {
120 let _ = self.name2entity.remove(&name);
121 }
122 let _ = self.world.despawn(entity);
123 }
124
125 pub fn get_entity_with_name(&self, name: &str) -> Option<Entity> {
128 self.name2entity.get(name).copied()
129 }
130
131 pub fn get_entity_mut_with_name(&mut self, name: &str) -> Option<EntityMut> {
134 let entity_opt = self.name2entity.get(name);
135 entity_opt.map(|ent| EntityMut::new(&mut self.world, *ent))
136 }
137
138 pub fn find_entity_with_id(&mut self, id: u8) -> Option<EntityRef> {
141 let entities = self.get_all_entities(false);
142
143 for entity in entities {
144 if u32::from(id) == entity.id() {
145 let e_ref = self.world.entity(entity).unwrap();
146 return Some(e_ref);
147 }
148 }
149 None
150 }
151
152 pub fn get_current_cam(&self) -> Option<Camera> {
155 let entity_opt = self.name2entity.get(GLOSS_CAM_NAME);
158 entity_opt.map(|ent| Camera::from_entity(*ent))
159 }
160
161 pub fn add_resource<T: gloss_hecs::Component>(&mut self, component: T) {
166 self.world.insert_one(self.entity_resource, component).unwrap();
167 }
168
169 pub fn remove_resource<T: gloss_hecs::Component>(&mut self) -> Result<T, gloss_hecs::ComponentError> {
177 self.world.remove_one::<T>(self.entity_resource) }
186
187 pub fn has_resource<T: gloss_hecs::Component>(&self) -> bool {
191 self.world.has::<T>(self.entity_resource).unwrap()
192 }
193
194 pub fn get_resource<'a, T: gloss_hecs::ComponentRef<'a>>(&'a self) -> Result<<T as ComponentRef<'a>>::Ref, gloss_hecs::ComponentError> {
200 self.world.get::<T>(self.entity_resource)
201 }
202
203 pub fn insert_if_doesnt_exist<T: gloss_hecs::Component + Default>(&mut self, entity: Entity) {
206 if !self.world.has::<T>(entity).unwrap() {
207 let _ = self.world.insert_one(entity, T::default());
208 }
209 }
210
211 pub fn get_comp<'a, T: gloss_hecs::ComponentRef<'a>>(
218 &'a self,
219 entity: &Entity,
220 ) -> Result<<T as ComponentRef<'a>>::Ref, gloss_hecs::ComponentError> {
221 self.world.get::<T>(*entity)
222 }
223
224 pub fn get_lights(&self, sorted_by_name: bool) -> Vec<Entity> {
227 let mut entities_with_name = Vec::new();
228 for (entity_light, (name, _)) in self.world.query::<(&Name, &LightEmit)>().iter() {
229 entities_with_name.push((entity_light, name.0.clone()));
230 }
231 if sorted_by_name {
233 entities_with_name.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
234 }
235 let entities = entities_with_name.iter().map(|x| x.0).collect();
236 entities
237 }
238
239 #[allow(clippy::missing_panics_doc)]
240 pub fn get_all_entities(&self, sorted_by_name: bool) -> Vec<Entity> {
241 let mut entities_with_name = Vec::new();
242 for (entity, name) in self.world.query::<&Name>().iter() {
243 entities_with_name.push((entity, name.0.clone()));
244 }
245 if sorted_by_name {
247 entities_with_name.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
248 }
249 let entities = entities_with_name.iter().map(|x| x.0).collect();
250 entities
251 }
252
253 #[allow(clippy::missing_panics_doc)]
254 pub fn get_renderables(&self, sorted_by_name: bool) -> Vec<Entity> {
255 let mut entities_with_name = Vec::new();
256 for (entity, (name, _)) in self.world.query::<(&Name, &Renderable)>().iter() {
257 entities_with_name.push((entity, name.0.clone()));
258 }
259 if sorted_by_name {
261 entities_with_name.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
262 }
263 let entities = entities_with_name.iter().map(|x| x.0).collect();
264 entities
265 }
266
267 pub fn get_renderable_names(&self) -> Vec<String> {
268 self.world
269 .query::<(&Name, &Renderable)>()
270 .iter()
271 .map(|(_, (name, _))| name.0.clone())
272 .collect()
273 }
274
275 #[allow(clippy::cast_possible_truncation)]
292 pub fn nr_renderables(&self) -> u32 {
293 self.get_renderables(false).len() as u32
294 }
295
296 pub fn get_bounding_points(&self) -> (na::Point3<f32>, na::Point3<f32>) {
301 let mut min_point_global = na::Point3::<f32>::new(f32::MAX, f32::MAX, f32::MAX);
302 let mut max_point_global = na::Point3::<f32>::new(f32::MIN, f32::MIN, f32::MIN);
303
304 for (_entity, (verts, model_matrix_opt, name, _)) in self.world.query::<(&Verts, Option<&ModelMatrix>, &Name, &Renderable)>().iter() {
305 if name.0 == GLOSS_FLOOR_NAME {
306 continue;
307 }
308
309 let model_matrix = if let Some(mm) = model_matrix_opt {
311 mm.clone()
312 } else {
313 ModelMatrix::default()
314 };
315
316 trace!("scale for mesh {}", name.0);
317
318 let min_coord_vec: Vec<f32> = verts.0.min_vec();
320 let max_coord_vec: Vec<f32> = verts.0.max_vec();
321 let min_point = na::Point3::<f32>::from_slice(&min_coord_vec);
325 let max_point = na::Point3::<f32>::from_slice(&max_coord_vec);
326
327 let min_point_w = model_matrix.0 * min_point;
329 let max_point_w = model_matrix.0 * max_point;
330
331 min_point_global = min_point_global.inf(&min_point_w);
333 max_point_global = max_point_global.sup(&max_point_w);
334 }
335
336 for (_entity, (bbox, name, _)) in self.world.query::<(&BoundingBox, &Name, &Renderable)>().without::<&Verts>().iter() {
339 if name.0 == GLOSS_FLOOR_NAME {
340 continue;
341 }
342 min_point_global = min_point_global.inf(&bbox.min);
343 max_point_global = max_point_global.sup(&bbox.max);
344 }
345
346 (min_point_global, max_point_global)
347 }
348
349 pub fn get_min_y(&self) -> f32 {
350 let mut min_y_global = f32::MAX;
351
352 for (_entity, (verts, model_matrix_opt, name, _)) in self.world.query::<(&Verts, Option<&ModelMatrix>, &Name, &Renderable)>().iter() {
353 if name.0 == GLOSS_FLOOR_NAME {
354 continue;
355 }
356
357 let model_matrix = if let Some(mm) = model_matrix_opt {
358 mm.clone()
359 } else {
360 ModelMatrix::default()
361 };
362
363 let v_world = geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
365 let min_y_cur = v_world.column(1).min();
366 min_y_global = min_y_global.min(min_y_cur);
367 }
368
369 for (_entity, (bbox, name, _)) in self.world.query::<(&BoundingBox, &Name, &Renderable)>().without::<&Verts>().iter() {
372 if name.0 == GLOSS_FLOOR_NAME {
373 continue;
374 }
375 min_y_global = min_y_global.min(bbox.min.y);
376 }
377
378 min_y_global
379 }
380
381 pub fn get_scale(&self) -> f32 {
384 if self.get_renderables(false).is_empty() {
385 error!("scale: no renderables, returning 1.0");
386 return 1.0;
387 }
388
389 let (min_point_global, max_point_global) = self.get_bounding_points();
390
391 let scale = (max_point_global - min_point_global).abs().max();
393
394 if scale.is_infinite() {
395 1.0
396 } else {
397 scale
398 }
399 }
400
401 pub fn get_centroid(&self) -> na::Point3<f32> {
402 if self.get_renderables(false).is_empty() {
403 error!("centroid: no renderables, returning 1.0");
404 return na::Point3::<f32>::origin();
405 }
406
407 let (min_point_global, max_point_global) = self.get_bounding_points();
408
409 min_point_global.lerp(&max_point_global, 0.5)
411 }
412
413 pub fn init_3_point_light(&self, light_config: &mut LightConfig, idx: usize, scale: f32, centroid: &na::Point3<f32>) {
414 let (mut dir_movement, intensity_at_point) = match idx {
415 0 => {
416 let dir_movement = na::Vector3::new(0.5, 0.6, 0.5);
417 let intensity_at_point = 2.9;
418 (dir_movement, intensity_at_point)
419 }
420 1 => {
421 let dir_movement = na::Vector3::new(-0.5, 0.6, 0.5);
422 let intensity_at_point = 1.0;
423 (dir_movement, intensity_at_point)
424 }
425 2 => {
426 let dir_movement = na::Vector3::new(-0.1, 0.6, -0.5);
427 let intensity_at_point = 3.5;
428 (dir_movement, intensity_at_point)
429 }
430 _ => {
432 let dir_movement = na::Vector3::new(0.0, 0.6, 0.5);
433 let intensity_at_point = 2.9;
434 (dir_movement, intensity_at_point)
435 }
436 };
437
438 dir_movement = dir_movement.normalize();
439 let lookat = centroid;
440 let position = centroid + dir_movement * 8.0 * scale; let intensity = Light::intensity_for_point(&position, lookat, intensity_at_point);
446
447 if light_config.position.is_none() {
448 light_config.position = Some(position);
449 }
450 if light_config.lookat.is_none() {
451 light_config.lookat = Some(*lookat);
452 }
453 if light_config.near.is_none() {
454 light_config.near = Some(scale * 0.5);
455 }
456 if light_config.far.is_none() {
457 light_config.far = Some(scale * 30.0);
458 }
459 if light_config.intensity.is_none() {
460 light_config.intensity = Some(intensity);
461 }
462 if light_config.range.is_none() {
463 light_config.range = Some(scale * 30.0);
464 }
465 if light_config.radius.is_none() {
466 light_config.radius = Some(scale * 1.5);
467 }
468 if light_config.shadow_res.is_none() {
469 light_config.shadow_res = Some(2048);
470 }
471 if light_config.shadow_bias_fixed.is_none() {
472 light_config.shadow_bias_fixed = Some(2e-6);
473 }
474 if light_config.shadow_bias.is_none() {
475 light_config.shadow_bias = Some(2e-6);
476 }
477 if light_config.shadow_bias_normal.is_none() {
478 light_config.shadow_bias_normal = Some(2e-6);
479 }
480 }
481
482 pub fn make_concrete_config(&self, config: &mut Config) {
483 let scale = self.get_scale();
484 let centroid = self.get_centroid();
485 let min_y = self.get_min_y();
486
487 let floor_scale_multiplier = 300.0;
489 if config.core.floor_scale.is_none() {
490 config.core.floor_scale = Some(scale * floor_scale_multiplier); }
492 if config.core.floor_origin.is_none() {
493 config.core.floor_origin = Some(na::Point3::<f32>::new(centroid.x, min_y, centroid.z));
494 }
495 if config.core.floor_uv_scale.is_none() {
496 config.core.floor_uv_scale = Some(scale * floor_scale_multiplier * 1.4);
497 }
498
499 let position = centroid + na::Vector3::z_axis().scale(2.0 * scale) + na::Vector3::y_axis().scale(0.5 * scale);
501 if config.scene.cam.position.is_none() {
502 config.scene.cam.position = Some(position);
503 }
504 if config.scene.cam.lookat.is_none() {
505 config.scene.cam.lookat = Some(centroid);
506 }
507 if config.scene.cam.near.is_none() {
508 let near = (centroid - position).norm() * 0.02;
509 config.scene.cam.near = Some(near);
510 }
511 if config.scene.cam.far.is_none() {
512 let far = (centroid - position).norm() * 50.0; config.scene.cam.far = Some(far);
515 }
516
517 if config.render.distance_fade_center.is_none() {
519 config.render.distance_fade_center = Some(centroid);
520 }
521 if config.render.distance_fade_start.is_none() {
522 config.render.distance_fade_start = Some(scale * 1.0);
523 }
524 if config.render.distance_fade_end.is_none() {
525 config.render.distance_fade_end = Some(scale * 8.5);
526 }
527
528 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
531 self.init_3_point_light(light_config, idx, scale, ¢roid);
532 }
533
534 config.set_concrete();
536 }
537
538 #[allow(clippy::cast_precision_loss)]
541 #[allow(clippy::needless_update)]
542 #[allow(clippy::too_many_lines)]
543 pub fn from_config(&mut self, config: &mut Config, width: u32, height: u32) {
544 let cam = self.get_current_cam().expect("Camera should be created");
546 if !self.world.has::<PosLookat>(cam.entity).unwrap() {
548 self.world
549 .insert(
550 cam.entity,
551 (
552 PosLookat::new(config.scene.cam.position.unwrap(), config.scene.cam.lookat.unwrap()),
553 CamController::new(
554 config.scene.cam.limit_max_dist,
555 config.scene.cam.limit_max_vertical_angle,
556 config.scene.cam.limit_min_vertical_angle,
557 ),
558 ),
559 )
560 .unwrap();
561 }
562 if !self.world.has::<Projection>(cam.entity).unwrap() {
564 let aspect_ratio = width as f32 / height as f32;
565 self.world
566 .insert(
567 cam.entity,
568 (Projection::WithFov(ProjectionWithFov {
569 aspect_ratio,
570 fovy: config.scene.cam.fovy,
571 near: config.scene.cam.near.unwrap(),
572 far: config.scene.cam.far.unwrap(),
573 ..Default::default()
574 }),),
575 )
576 .unwrap();
577 }
578
579 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
581 let entity = self
582 .get_or_create_hidden_entity(("light_".to_owned() + idx.to_string().as_str()).as_str())
583 .insert(PosLookat::new(light_config.position.unwrap(), light_config.lookat.unwrap()))
584 .insert(Projection::WithFov(ProjectionWithFov {
585 aspect_ratio: 1.0,
586 fovy: light_config.fovy, near: light_config.near.unwrap(),
588 far: light_config.far.unwrap(),
589 ..Default::default()
590 }))
591 .insert(LightEmit {
592 color: light_config.color,
593 intensity: light_config.intensity.unwrap(),
594 range: light_config.range.unwrap(),
595 radius: light_config.radius.unwrap(),
596 ..Default::default()
597 })
598 .entity;
599 let shadow_res = light_config.shadow_res.unwrap_or(0);
601 if shadow_res != 0 {
602 self.world
603 .insert_one(
604 entity,
605 ShadowCaster {
606 shadow_res: light_config.shadow_res.unwrap(),
607 shadow_bias_fixed: light_config.shadow_bias_fixed.unwrap(),
608 shadow_bias: light_config.shadow_bias.unwrap(),
609 shadow_bias_normal: light_config.shadow_bias_normal.unwrap(),
610 },
611 )
612 .ok();
613 }
614 }
616
617 if config.core.auto_add_floor {
619 self.create_floor(config);
620 }
621
622 config.set_consumed();
624 }
625
626 pub fn create_floor(&mut self, config: &Config) {
627 #[allow(clippy::cast_possible_truncation)]
628 #[allow(clippy::cast_sign_loss)]
629 let floor_builder = match config.core.floor_type {
630 FloorType::Solid => builders::build_plane(
631 config.core.floor_origin.unwrap(),
632 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
633 config.core.floor_scale.unwrap(),
634 config.core.floor_scale.unwrap(),
635 false,
636 ),
637 FloorType::Grid => builders::build_grid(
638 config.core.floor_origin.unwrap(),
639 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
640 (config.core.floor_scale.unwrap() / 1.0) as u32,
641 (config.core.floor_scale.unwrap() / 1.0) as u32,
642 config.core.floor_scale.unwrap(),
643 config.core.floor_scale.unwrap(),
644 false,
645 ),
646 };
647
648 #[allow(clippy::cast_possible_truncation)]
649 let floor_ent = self
650 .get_or_create_entity(GLOSS_FLOOR_NAME)
651 .insert_builder(floor_builder)
652 .insert(VisMesh {
653 show_mesh: config.core.floor_type == FloorType::Solid,
654 solid_color: na::Vector4::<f32>::new(0.08, 0.08, 0.08, 1.0), perceptual_roughness: 0.70,
657 uv_scale: config.core.floor_uv_scale.unwrap(),
658 color_type: MeshColorType::Texture,
660 ..Default::default()
661 })
662 .insert(VisPoints::default())
663 .insert(VisLines {
664 show_lines: config.core.floor_type == FloorType::Grid,
665 line_color: na::Vector4::<f32>::new(0.2, 0.2, 0.2, 1.0),
666 line_width: config.core.floor_grid_line_width,
667 antialias_edges: true,
668 ..Default::default()
669 })
670 .entity();
671
672 if config.core.floor_texture == FloorTexture::Checkerboard {
673 let texture_checkerboard = DiffuseImg::new_from_buf(
674 CHECKERBOARD_BYTES,
675 &ImgConfig {
676 mipmap_generation_cpu: true, ..Default::default()
679 },
680 );
681 let _ = self.world.insert_one(floor_ent, texture_checkerboard);
682 }
683 }
684
685 #[allow(clippy::missing_panics_doc)]
686 pub fn get_floor(&self) -> Option<Actor> {
687 let ent_opt = self.name2entity.get(GLOSS_FLOOR_NAME);
688 ent_opt.map(|ent| Actor::from_entity(*ent))
689 }
690
691 pub fn has_floor(&self) -> bool {
692 self.name2entity.get(GLOSS_FLOOR_NAME).is_some()
693 }
694
695 pub fn create_camera(&mut self) {
696 Camera::new(GLOSS_CAM_NAME, self, false);
697 }
698
699 pub fn remove_all_gpu_components(&mut self) {
700 let mut command_buffer = CommandBuffer::new();
702 for (entity, ()) in self.world.query::<()>().iter() {
703 command_buffer.remove_one::<VertsGPU>(entity);
704 command_buffer.remove_one::<UVsGPU>(entity);
705 command_buffer.remove_one::<NormalsGPU>(entity);
706 command_buffer.remove_one::<ColorsGPU>(entity);
707 command_buffer.remove_one::<EdgesGPU>(entity);
708 command_buffer.remove_one::<FacesGPU>(entity);
709 command_buffer.remove_one::<TangentsGPU>(entity);
710 command_buffer.remove_one::<DiffuseTex>(entity);
711 command_buffer.remove_one::<NormalTex>(entity);
712 command_buffer.remove_one::<MetalnessTex>(entity);
713 command_buffer.remove_one::<RoughnessTex>(entity);
714 command_buffer.remove_one::<EnvironmentMapGpu>(entity);
715 command_buffer.remove_one::<ShadowMap>(entity);
716 }
717
718 command_buffer.run_on(&mut self.world);
719 }
720}
721
722pub struct EntityMut<'w> {
724 world: &'w mut World,
725 entity: Entity,
726 }
728
729impl<'w> EntityMut<'w> {
731 pub(crate) fn new(world: &'w mut World, entity: Entity) -> Self {
732 EntityMut { world, entity }
733 }
734
735 pub fn insert<T: Component>(&mut self, component: T) -> &mut Self {
738 self.insert_bundle((component,))
739 }
740 pub fn insert_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
743 let _ = self.world.insert(self.entity, bundle);
744 self
745 }
746 pub fn insert_builder(&mut self, mut builder: EntityBuilder) -> &mut Self {
749 let _ = self.world.insert(self.entity, builder.build());
750 self
751 }
752 pub fn get_comp<'a, T: gloss_hecs::ComponentRef<'a>>(&'a self) -> <T as ComponentRef<'a>>::Ref {
756 let comp = self.world.get::<T>(self.entity).unwrap();
757 comp
758 }
759 pub fn entity(&self) -> Entity {
760 self.entity
761 }
762}