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 CamController, ColorsGPU, DiffuseImg, DiffuseTex, EdgesGPU, EnvironmentMapGpu, FacesGPU, ImgConfig, LightEmit, MeshColorType, MetalnessTex,
12 ModelMatrix, Name, NormalTex, NormalsGPU, PosLookat, Projection, ProjectionWithFov, Renderable, RoughnessTex, ShadowCaster, ShadowMap,
13 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 (min_point_global, max_point_global)
337 }
338
339 pub fn get_min_y(&self) -> f32 {
340 let mut min_y_global = f32::MAX;
341
342 for (_entity, (verts, model_matrix_opt, name, _)) in self.world.query::<(&Verts, Option<&ModelMatrix>, &Name, &Renderable)>().iter() {
343 if name.0 == GLOSS_FLOOR_NAME {
344 continue;
345 }
346
347 let model_matrix = if let Some(mm) = model_matrix_opt {
348 mm.clone()
349 } else {
350 ModelMatrix::default()
351 };
352
353 let v_world = geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
355 let min_y_cur = v_world.column(1).min();
356 min_y_global = min_y_global.min(min_y_cur);
357 }
358
359 min_y_global
360 }
361
362 pub fn get_scale(&self) -> f32 {
365 if self.get_renderables(false).is_empty() {
366 error!("scale: no renderables, returning 1.0");
367 return 1.0;
368 }
369
370 let (min_point_global, max_point_global) = self.get_bounding_points();
371
372 let scale = (max_point_global - min_point_global).abs().max();
374
375 if scale.is_infinite() {
376 1.0
377 } else {
378 scale
379 }
380 }
381
382 pub fn get_centroid(&self) -> na::Point3<f32> {
383 if self.get_renderables(false).is_empty() {
384 error!("centroid: no renderables, returning 1.0");
385 return na::Point3::<f32>::origin();
386 }
387
388 let (min_point_global, max_point_global) = self.get_bounding_points();
389
390 min_point_global.lerp(&max_point_global, 0.5)
392 }
393
394 pub fn init_3_point_light(&self, light_config: &mut LightConfig, idx: usize, scale: f32, centroid: &na::Point3<f32>) {
395 let (mut dir_movement, intensity_at_point) = match idx {
396 0 => {
397 let dir_movement = na::Vector3::new(0.5, 0.6, 0.5);
398 let intensity_at_point = 2.9;
399 (dir_movement, intensity_at_point)
400 }
401 1 => {
402 let dir_movement = na::Vector3::new(-0.5, 0.6, 0.5);
403 let intensity_at_point = 1.0;
404 (dir_movement, intensity_at_point)
405 }
406 2 => {
407 let dir_movement = na::Vector3::new(-0.1, 0.6, -0.5);
408 let intensity_at_point = 3.5;
409 (dir_movement, intensity_at_point)
410 }
411 _ => {
413 let dir_movement = na::Vector3::new(0.0, 0.6, 0.5);
414 let intensity_at_point = 2.9;
415 (dir_movement, intensity_at_point)
416 }
417 };
418
419 dir_movement = dir_movement.normalize();
420 let lookat = centroid;
421 let position = centroid + dir_movement * 8.0 * scale; let intensity = Light::intensity_for_point(&position, lookat, intensity_at_point);
427
428 if light_config.position.is_none() {
429 light_config.position = Some(position);
430 }
431 if light_config.lookat.is_none() {
432 light_config.lookat = Some(*lookat);
433 }
434 if light_config.near.is_none() {
435 light_config.near = Some(scale * 0.5);
436 }
437 if light_config.far.is_none() {
438 light_config.far = Some(scale * 30.0);
439 }
440 if light_config.intensity.is_none() {
441 light_config.intensity = Some(intensity);
442 }
443 if light_config.range.is_none() {
444 light_config.range = Some(scale * 30.0);
445 }
446 if light_config.radius.is_none() {
447 light_config.radius = Some(scale * 1.5);
448 }
449 if light_config.shadow_res.is_none() {
450 light_config.shadow_res = Some(2048);
451 }
452 if light_config.shadow_bias_fixed.is_none() {
453 light_config.shadow_bias_fixed = Some(2e-6);
454 }
455 if light_config.shadow_bias.is_none() {
456 light_config.shadow_bias = Some(2e-6);
457 }
458 if light_config.shadow_bias_normal.is_none() {
459 light_config.shadow_bias_normal = Some(2e-6);
460 }
461 }
462
463 pub fn make_concrete_config(&self, config: &mut Config) {
464 let scale = self.get_scale();
465 let centroid = self.get_centroid();
466 let min_y = self.get_min_y();
467
468 let floor_scale_multiplier = 300.0;
470 if config.core.floor_scale.is_none() {
471 config.core.floor_scale = Some(scale * floor_scale_multiplier); }
473 if config.core.floor_origin.is_none() {
474 config.core.floor_origin = Some(na::Point3::<f32>::new(centroid.x, min_y, centroid.z));
475 }
476 if config.core.floor_uv_scale.is_none() {
477 config.core.floor_uv_scale = Some(scale * floor_scale_multiplier * 1.4);
478 }
479
480 let position = centroid + na::Vector3::z_axis().scale(2.0 * scale) + na::Vector3::y_axis().scale(0.5 * scale);
482 if config.scene.cam.position.is_none() {
483 config.scene.cam.position = Some(position);
484 }
485 if config.scene.cam.lookat.is_none() {
486 config.scene.cam.lookat = Some(centroid);
487 }
488 if config.scene.cam.near.is_none() {
489 let near = (centroid - position).norm() * 0.02;
490 config.scene.cam.near = Some(near);
491 }
492 if config.scene.cam.far.is_none() {
493 let far = (centroid - position).norm() * 50.0; config.scene.cam.far = Some(far);
496 }
497
498 if config.render.distance_fade_center.is_none() {
500 config.render.distance_fade_center = Some(centroid);
501 }
502 if config.render.distance_fade_start.is_none() {
503 config.render.distance_fade_start = Some(scale * 1.0);
504 }
505 if config.render.distance_fade_end.is_none() {
506 config.render.distance_fade_end = Some(scale * 8.5);
507 }
508
509 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
512 self.init_3_point_light(light_config, idx, scale, ¢roid);
513 }
514
515 config.set_concrete();
517 }
518
519 #[allow(clippy::cast_precision_loss)]
522 #[allow(clippy::needless_update)]
523 #[allow(clippy::too_many_lines)]
524 pub fn from_config(&mut self, config: &mut Config, width: u32, height: u32) {
525 let cam = self.get_current_cam().expect("Camera should be created");
527 if !self.world.has::<PosLookat>(cam.entity).unwrap() {
529 self.world
530 .insert(
531 cam.entity,
532 (
533 PosLookat::new(config.scene.cam.position.unwrap(), config.scene.cam.lookat.unwrap()),
534 CamController::new(
535 config.scene.cam.limit_max_dist,
536 config.scene.cam.limit_max_vertical_angle,
537 config.scene.cam.limit_min_vertical_angle,
538 ),
539 ),
540 )
541 .unwrap();
542 }
543 if !self.world.has::<Projection>(cam.entity).unwrap() {
545 let aspect_ratio = width as f32 / height as f32;
546 self.world
547 .insert(
548 cam.entity,
549 (Projection::WithFov(ProjectionWithFov {
550 aspect_ratio,
551 fovy: config.scene.cam.fovy,
552 near: config.scene.cam.near.unwrap(),
553 far: config.scene.cam.far.unwrap(),
554 ..Default::default()
555 }),),
556 )
557 .unwrap();
558 }
559
560 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
562 let entity = self
563 .get_or_create_hidden_entity(("light_".to_owned() + idx.to_string().as_str()).as_str())
564 .insert(PosLookat::new(light_config.position.unwrap(), light_config.lookat.unwrap()))
565 .insert(Projection::WithFov(ProjectionWithFov {
566 aspect_ratio: 1.0,
567 fovy: light_config.fovy, near: light_config.near.unwrap(),
569 far: light_config.far.unwrap(),
570 ..Default::default()
571 }))
572 .insert(LightEmit {
573 color: light_config.color,
574 intensity: light_config.intensity.unwrap(),
575 range: light_config.range.unwrap(),
576 radius: light_config.radius.unwrap(),
577 ..Default::default()
578 })
579 .entity;
580 let shadow_res = light_config.shadow_res.unwrap_or(0);
582 if shadow_res != 0 {
583 self.world
584 .insert_one(
585 entity,
586 ShadowCaster {
587 shadow_res: light_config.shadow_res.unwrap(),
588 shadow_bias_fixed: light_config.shadow_bias_fixed.unwrap(),
589 shadow_bias: light_config.shadow_bias.unwrap(),
590 shadow_bias_normal: light_config.shadow_bias_normal.unwrap(),
591 },
592 )
593 .ok();
594 }
595 }
597
598 if config.core.auto_add_floor {
600 self.create_floor(config);
601 }
602
603 config.set_consumed();
605 }
606
607 pub fn create_floor(&mut self, config: &Config) {
608 #[allow(clippy::cast_possible_truncation)]
609 #[allow(clippy::cast_sign_loss)]
610 let floor_builder = match config.core.floor_type {
611 FloorType::Solid => builders::build_plane(
612 config.core.floor_origin.unwrap(),
613 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
614 config.core.floor_scale.unwrap(),
615 config.core.floor_scale.unwrap(),
616 false,
617 ),
618 FloorType::Grid => builders::build_grid(
619 config.core.floor_origin.unwrap(),
620 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
621 (config.core.floor_scale.unwrap() / 1.0) as u32,
622 (config.core.floor_scale.unwrap() / 1.0) as u32,
623 config.core.floor_scale.unwrap(),
624 config.core.floor_scale.unwrap(),
625 false,
626 ),
627 };
628
629 #[allow(clippy::cast_possible_truncation)]
630 let floor_ent = self
631 .get_or_create_entity(GLOSS_FLOOR_NAME)
632 .insert_builder(floor_builder)
633 .insert(VisMesh {
634 show_mesh: config.core.floor_type == FloorType::Solid,
635 solid_color: na::Vector4::<f32>::new(0.08, 0.08, 0.08, 1.0), perceptual_roughness: 0.70,
638 uv_scale: config.core.floor_uv_scale.unwrap(),
639 color_type: MeshColorType::Texture,
641 ..Default::default()
642 })
643 .insert(VisPoints::default())
644 .insert(VisLines {
645 show_lines: config.core.floor_type == FloorType::Grid,
646 line_color: na::Vector4::<f32>::new(0.2, 0.2, 0.2, 1.0),
647 line_width: config.core.floor_grid_line_width,
648 antialias_edges: true,
649 ..Default::default()
650 })
651 .entity();
652
653 if config.core.floor_texture == FloorTexture::Checkerboard {
654 let texture_checkerboard = DiffuseImg::new_from_buf(
655 CHECKERBOARD_BYTES,
656 &ImgConfig {
657 mipmap_generation_cpu: true, ..Default::default()
660 },
661 );
662 let _ = self.world.insert_one(floor_ent, texture_checkerboard);
663 }
664 }
665
666 #[allow(clippy::missing_panics_doc)]
667 pub fn get_floor(&self) -> Option<Actor> {
668 let ent_opt = self.name2entity.get(GLOSS_FLOOR_NAME);
669 ent_opt.map(|ent| Actor::from_entity(*ent))
670 }
671
672 pub fn has_floor(&self) -> bool {
673 self.name2entity.get(GLOSS_FLOOR_NAME).is_some()
674 }
675
676 pub fn remove_all_gpu_components(&mut self) {
677 let mut command_buffer = CommandBuffer::new();
679 for (entity, ()) in self.world.query::<()>().iter() {
680 command_buffer.remove_one::<VertsGPU>(entity);
681 command_buffer.remove_one::<UVsGPU>(entity);
682 command_buffer.remove_one::<NormalsGPU>(entity);
683 command_buffer.remove_one::<ColorsGPU>(entity);
684 command_buffer.remove_one::<EdgesGPU>(entity);
685 command_buffer.remove_one::<FacesGPU>(entity);
686 command_buffer.remove_one::<TangentsGPU>(entity);
687 command_buffer.remove_one::<DiffuseTex>(entity);
688 command_buffer.remove_one::<NormalTex>(entity);
689 command_buffer.remove_one::<MetalnessTex>(entity);
690 command_buffer.remove_one::<RoughnessTex>(entity);
691 command_buffer.remove_one::<EnvironmentMapGpu>(entity);
692 command_buffer.remove_one::<ShadowMap>(entity);
693 }
694
695 command_buffer.run_on(&mut self.world);
696 }
697}
698
699pub struct EntityMut<'w> {
701 world: &'w mut World,
702 entity: Entity,
703 }
705
706impl<'w> EntityMut<'w> {
708 pub(crate) fn new(world: &'w mut World, entity: Entity) -> Self {
709 EntityMut { world, entity }
710 }
711
712 pub fn insert<T: Component>(&mut self, component: T) -> &mut Self {
715 self.insert_bundle((component,))
716 }
717 pub fn insert_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
720 let _ = self.world.insert(self.entity, bundle);
721 self
722 }
723 pub fn insert_builder(&mut self, mut builder: EntityBuilder) -> &mut Self {
726 let _ = self.world.insert(self.entity, builder.build());
727 self
728 }
729 pub fn get_comp<'a, T: gloss_hecs::ComponentRef<'a>>(&'a self) -> <T as ComponentRef<'a>>::Ref {
733 let comp = self.world.get::<T>(self.entity).unwrap();
734 comp
735 }
736 pub fn entity(&self) -> Entity {
737 self.entity
738 }
739}