1#![allow(clippy::doc_markdown)]
2
3use gloss_hecs::{CommandBuffer, Component, ComponentRef, DynamicBundle, Entity, EntityBuilder, World};
4use log::{error, trace};
5
6use crate::{
7 actor::Actor,
8 camera::Camera,
9 components::{
10 CamController, ColorsGPU, DiffuseImg, DiffuseTex, EdgesGPU, EnvironmentMapGpu, FacesGPU, ImgConfig, LightEmit, MeshColorType, MetalnessTex,
11 ModelMatrix, Name, NormalTex, NormalsGPU, PosLookat, Projection, ProjectionWithFov, Renderable, RoughnessTex, ShadowCaster, ShadowMap,
12 TangentsGPU, UVsGPU, Verts, VertsGPU, VisLines, VisMesh, VisPoints,
13 },
14 config::{Config, FloorTexture, FloorType, LightConfig},
15 geom::Geom,
16 light::Light,
17};
18use gloss_utils::abi_stable_aliases::std_types::{RHashMap, RString};
19#[cfg(not(target_arch = "wasm32"))]
20use gloss_utils::abi_stable_aliases::StableAbi;
21use gloss_utils::tensor::{DynamicMatrixOps, DynamicTensorOps};
22use nalgebra as na;
23
24pub static GLOSS_FLOOR_NAME: &str = "floor";
25pub static GLOSS_CAM_NAME: &str = "gloss_camera";
26
27static CHECKERBOARD_BYTES: &[u8; 2324] = include_bytes!("../data/checkerboard.png");
29
30#[repr(C)]
32#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
33#[allow(non_upper_case_globals, non_camel_case_types)]
34pub struct Scene {
35 pub world: World,
36 name2entity: RHashMap<RString, Entity>,
37 pub command_buffer: CommandBuffer, entity_resource: Entity, }
40impl Default for Scene {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl Scene {
47 pub fn new() -> Self {
48 let mut world = World::default();
49 let name2entity = RHashMap::<RString, Entity>::new();
50 let command_buffer = CommandBuffer::new();
51 let entity_resource = world.spawn((Name("entity_resource".to_string()),));
52 Self {
53 world,
54 name2entity,
55 command_buffer,
56 entity_resource,
57 }
58 }
59
60 pub fn get_entity_resource(&self) -> Entity {
62 self.entity_resource
63 }
64
65 pub fn get_unused_name(&self) -> String {
67 let mut cur_nr = self.get_renderables(false).len();
68 loop {
69 let name = String::from("ent_") + &cur_nr.to_string();
70 let r_name = RString::from(name.clone());
71 if !self.name2entity.contains_key(&r_name) {
72 return name;
73 }
74 cur_nr += 1;
75 }
76 }
77
78 pub fn get_or_create_hidden_entity(&mut self, name: &str) -> EntityMut {
82 let r_name = RString::from(name.to_string());
83 let entity_ref = self
84 .name2entity
85 .entry(r_name)
86 .or_insert_with(|| self.world.spawn((Name(name.to_string()),))); EntityMut::new(&mut self.world, *entity_ref)
88 }
89
90 pub fn get_or_create_entity(&mut self, name: &str) -> EntityMut {
94 let r_name = RString::from(name.to_string());
95 let entity_ref = self
96 .name2entity
97 .entry(r_name)
98 .or_insert_with(|| self.world.spawn((Name(name.to_string()), Renderable))); EntityMut::new(&mut self.world, *entity_ref)
100 }
101
102 pub fn despawn_with_name(&mut self, name: &str) {
104 if let Some(entity) = self.get_entity_with_name(name) {
107 let _ = self.world.despawn(entity);
108 let r_name = RString::from(name.to_string());
109 let _ = self.name2entity.remove(&r_name);
110 }
111 }
112
113 pub fn despawn(&mut self, entity: Entity) {
114 let name = self.get_comp::<&Name>(&entity).map(|x| RString::from(x.0.to_string()));
117 if let Ok(name) = name {
118 let _ = self.name2entity.remove(&name);
119 }
120 let _ = self.world.despawn(entity);
121 }
122
123 pub fn get_entity_with_name(&self, name: &str) -> Option<Entity> {
126 self.name2entity.get(name).copied()
127 }
128
129 pub fn get_entity_mut_with_name(&mut self, name: &str) -> Option<EntityMut> {
132 let entity_opt = self.name2entity.get(name);
133 entity_opt.map(|ent| EntityMut::new(&mut self.world, *ent))
134 }
135
136 pub fn get_current_cam(&self) -> Option<Camera> {
139 let entity_opt = self.name2entity.get(GLOSS_CAM_NAME);
142 entity_opt.map(|ent| Camera::from_entity(*ent))
143 }
144
145 pub fn add_resource<T: gloss_hecs::Component>(&mut self, component: T) {
150 self.world.insert_one(self.entity_resource, component).unwrap();
151 }
152
153 pub fn remove_resource<T: gloss_hecs::Component>(&mut self) -> Result<T, gloss_hecs::ComponentError> {
161 self.world.remove_one::<T>(self.entity_resource) }
170
171 pub fn has_resource<T: gloss_hecs::Component>(&self) -> bool {
175 self.world.has::<T>(self.entity_resource).unwrap()
176 }
177
178 pub fn get_resource<'a, T: gloss_hecs::ComponentRef<'a>>(&'a self) -> Result<<T as ComponentRef<'a>>::Ref, gloss_hecs::ComponentError> {
184 self.world.get::<T>(self.entity_resource)
185 }
186
187 pub fn insert_if_doesnt_exist<T: gloss_hecs::Component + Default>(&mut self, entity: Entity) {
190 if !self.world.has::<T>(entity).unwrap() {
191 let _ = self.world.insert_one(entity, T::default());
192 }
193 }
194
195 pub fn get_comp<'a, T: gloss_hecs::ComponentRef<'a>>(
202 &'a self,
203 entity: &Entity,
204 ) -> Result<<T as ComponentRef<'a>>::Ref, gloss_hecs::ComponentError> {
205 self.world.get::<T>(*entity)
206 }
207
208 pub fn get_lights(&self, sorted_by_name: bool) -> Vec<Entity> {
211 let mut entities_with_name = Vec::new();
212 for (entity_light, (name, _)) in self.world.query::<(&Name, &LightEmit)>().iter() {
213 entities_with_name.push((entity_light, name.0.clone()));
214 }
215 if sorted_by_name {
217 entities_with_name.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
218 }
219 let entities = entities_with_name.iter().map(|x| x.0).collect();
220 entities
221 }
222
223 #[allow(clippy::missing_panics_doc)]
224 pub fn get_renderables(&self, sorted_by_name: bool) -> Vec<Entity> {
225 let mut entities_with_name = Vec::new();
226 for (entity, (name, _)) in self.world.query::<(&Name, &Renderable)>().iter() {
227 entities_with_name.push((entity, name.0.clone()));
228 }
229 if sorted_by_name {
231 entities_with_name.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
232 }
233 let entities = entities_with_name.iter().map(|x| x.0).collect();
234 entities
235 }
236
237 pub fn get_renderable_names(&self) -> Vec<String> {
238 self.world
239 .query::<(&Name, &Renderable)>()
240 .iter()
241 .map(|(_, (name, _))| name.0.clone())
242 .collect()
243 }
244
245 #[allow(clippy::cast_possible_truncation)]
262 pub fn nr_renderables(&self) -> u32 {
263 self.get_renderables(false).len() as u32
264 }
265
266 pub fn get_bounding_points(&self) -> (na::Point3<f32>, na::Point3<f32>) {
271 let mut min_point_global = na::Point3::<f32>::new(f32::MAX, f32::MAX, f32::MAX);
272 let mut max_point_global = na::Point3::<f32>::new(f32::MIN, f32::MIN, f32::MIN);
273
274 for (_entity, (verts, model_matrix_opt, name, _)) in self.world.query::<(&Verts, Option<&ModelMatrix>, &Name, &Renderable)>().iter() {
275 if name.0 == GLOSS_FLOOR_NAME {
276 continue;
277 }
278
279 let model_matrix = if let Some(mm) = model_matrix_opt {
281 mm.clone()
282 } else {
283 ModelMatrix::default()
284 };
285
286 trace!("scale for mesh {}", name.0);
287
288 let min_coord_vec: Vec<f32> = verts.0.min_vec();
290 let max_coord_vec: Vec<f32> = verts.0.max_vec();
291 let min_point = na::Point3::<f32>::from_slice(&min_coord_vec);
295 let max_point = na::Point3::<f32>::from_slice(&max_coord_vec);
296
297 let min_point_w = model_matrix.0 * min_point;
299 let max_point_w = model_matrix.0 * max_point;
300
301 min_point_global = min_point_global.inf(&min_point_w);
303 max_point_global = max_point_global.sup(&max_point_w);
304 }
305
306 (min_point_global, max_point_global)
307 }
308
309 pub fn get_min_y(&self) -> f32 {
310 let mut min_y_global = f32::MAX;
311
312 for (_entity, (verts, model_matrix_opt, name, _)) in self.world.query::<(&Verts, Option<&ModelMatrix>, &Name, &Renderable)>().iter() {
313 if name.0 == GLOSS_FLOOR_NAME {
314 continue;
315 }
316
317 let model_matrix = if let Some(mm) = model_matrix_opt {
318 mm.clone()
319 } else {
320 ModelMatrix::default()
321 };
322
323 let v_world = Geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
325 let min_y_cur = v_world.column(1).min();
326 min_y_global = min_y_global.min(min_y_cur);
327 }
328
329 min_y_global
330 }
331
332 pub fn get_scale(&self) -> f32 {
335 if self.get_renderables(false).is_empty() {
336 error!("scale: no renderables, returning 1.0");
337 return 1.0;
338 }
339
340 let (min_point_global, max_point_global) = self.get_bounding_points();
341
342 let scale = (max_point_global - min_point_global).abs().max();
344
345 if scale.is_infinite() {
346 1.0
347 } else {
348 scale
349 }
350 }
351
352 pub fn get_centroid(&self) -> na::Point3<f32> {
353 if self.get_renderables(false).is_empty() {
354 error!("centroid: no renderables, returning 1.0");
355 return na::Point3::<f32>::origin();
356 }
357
358 let (min_point_global, max_point_global) = self.get_bounding_points();
359
360 min_point_global.lerp(&max_point_global, 0.5)
362 }
363
364 pub fn init_3_point_light(&self, light_config: &mut LightConfig, idx: usize, scale: f32, centroid: &na::Point3<f32>) {
365 let (mut dir_movement, intensity_at_point) = match idx {
366 0 => {
367 let dir_movement = na::Vector3::new(0.5, 0.6, 0.5);
368 let intensity_at_point = 2.9;
369 (dir_movement, intensity_at_point)
370 }
371 1 => {
372 let dir_movement = na::Vector3::new(-0.5, 0.6, 0.5);
373 let intensity_at_point = 1.0;
374 (dir_movement, intensity_at_point)
375 }
376 2 => {
377 let dir_movement = na::Vector3::new(-0.1, 0.6, -0.5);
378 let intensity_at_point = 3.5;
379 (dir_movement, intensity_at_point)
380 }
381 _ => {
383 let dir_movement = na::Vector3::new(0.0, 0.6, 0.5);
384 let intensity_at_point = 2.9;
385 (dir_movement, intensity_at_point)
386 }
387 };
388
389 dir_movement = dir_movement.normalize();
390 let lookat = centroid;
391 let position = centroid + dir_movement * 8.0 * scale; let intensity = Light::intensity_for_point(&position, lookat, intensity_at_point);
397
398 if light_config.position.is_none() {
399 light_config.position = Some(position);
400 }
401 if light_config.lookat.is_none() {
402 light_config.lookat = Some(*lookat);
403 }
404 if light_config.near.is_none() {
405 light_config.near = Some(scale * 0.5);
406 }
407 if light_config.far.is_none() {
408 light_config.far = Some(scale * 30.0);
409 }
410 if light_config.intensity.is_none() {
411 light_config.intensity = Some(intensity);
412 }
413 if light_config.range.is_none() {
414 light_config.range = Some(scale * 30.0);
415 }
416 if light_config.radius.is_none() {
417 light_config.radius = Some(scale * 1.5);
418 }
419 if light_config.shadow_res.is_none() {
420 light_config.shadow_res = Some(2048);
421 }
422 if light_config.shadow_bias_fixed.is_none() {
423 light_config.shadow_bias_fixed = Some(2e-6);
424 }
425 if light_config.shadow_bias.is_none() {
426 light_config.shadow_bias = Some(2e-6);
427 }
428 if light_config.shadow_bias_normal.is_none() {
429 light_config.shadow_bias_normal = Some(2e-6);
430 }
431 }
432
433 pub fn make_concrete_config(&self, config: &mut Config) {
434 let scale = self.get_scale();
435 let centroid = self.get_centroid();
436 let min_y = self.get_min_y();
437
438 let floor_scale_multiplier = 300.0;
440 if config.core.floor_scale.is_none() {
441 config.core.floor_scale = Some(scale * floor_scale_multiplier); }
443 if config.core.floor_origin.is_none() {
444 config.core.floor_origin = Some(na::Point3::<f32>::new(centroid.x, min_y, centroid.z));
445 }
446 if config.core.floor_uv_scale.is_none() {
447 config.core.floor_uv_scale = Some(scale * floor_scale_multiplier * 1.4);
448 }
449
450 let position = centroid + na::Vector3::z_axis().scale(2.0 * scale) + na::Vector3::y_axis().scale(0.5 * scale);
452 if config.scene.cam.position.is_none() {
453 config.scene.cam.position = Some(position);
454 }
455 if config.scene.cam.lookat.is_none() {
456 config.scene.cam.lookat = Some(centroid);
457 }
458 if config.scene.cam.near.is_none() {
459 let near = (centroid - position).norm() * 0.02;
460 config.scene.cam.near = Some(near);
461 }
462 if config.scene.cam.far.is_none() {
463 let far = (centroid - position).norm() * 50.0; config.scene.cam.far = Some(far);
466 }
467
468 if config.render.distance_fade_center.is_none() {
470 config.render.distance_fade_center = Some(centroid);
471 }
472 if config.render.distance_fade_start.is_none() {
473 config.render.distance_fade_start = Some(scale * 1.0);
474 }
475 if config.render.distance_fade_end.is_none() {
476 config.render.distance_fade_end = Some(scale * 8.5);
477 }
478
479 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
482 self.init_3_point_light(light_config, idx, scale, ¢roid);
483 }
484
485 config.set_concrete();
487 }
488
489 #[allow(clippy::cast_precision_loss)]
492 #[allow(clippy::needless_update)]
493 #[allow(clippy::too_many_lines)]
494 pub fn from_config(&mut self, config: &mut Config, width: u32, height: u32) {
495 let cam = self.get_current_cam().expect("Camera should be created");
497 if !self.world.has::<PosLookat>(cam.entity).unwrap() {
499 self.world
500 .insert(
501 cam.entity,
502 (
503 PosLookat::new(config.scene.cam.position.unwrap(), config.scene.cam.lookat.unwrap()),
504 CamController::new(
505 config.scene.cam.limit_max_dist,
506 config.scene.cam.limit_max_vertical_angle,
507 config.scene.cam.limit_min_vertical_angle,
508 ),
509 ),
510 )
511 .unwrap();
512 }
513 if !self.world.has::<Projection>(cam.entity).unwrap() {
515 let aspect_ratio = width as f32 / height as f32;
516 self.world
517 .insert(
518 cam.entity,
519 (Projection::WithFov(ProjectionWithFov {
520 aspect_ratio,
521 fovy: config.scene.cam.fovy,
522 near: config.scene.cam.near.unwrap(),
523 far: config.scene.cam.far.unwrap(),
524 ..Default::default()
525 }),),
526 )
527 .unwrap();
528 }
529
530 for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
532 let entity = self
533 .get_or_create_hidden_entity(("light_".to_owned() + idx.to_string().as_str()).as_str())
534 .insert(PosLookat::new(light_config.position.unwrap(), light_config.lookat.unwrap()))
535 .insert(Projection::WithFov(ProjectionWithFov {
536 aspect_ratio: 1.0,
537 fovy: light_config.fovy, near: light_config.near.unwrap(),
539 far: light_config.far.unwrap(),
540 ..Default::default()
541 }))
542 .insert(LightEmit {
543 color: light_config.color,
544 intensity: light_config.intensity.unwrap(),
545 range: light_config.range.unwrap(),
546 radius: light_config.radius.unwrap(),
547 ..Default::default()
548 })
549 .entity;
550 let shadow_res = light_config.shadow_res.unwrap_or(0);
552 if shadow_res != 0 {
553 self.world
554 .insert_one(
555 entity,
556 ShadowCaster {
557 shadow_res: light_config.shadow_res.unwrap(),
558 shadow_bias_fixed: light_config.shadow_bias_fixed.unwrap(),
559 shadow_bias: light_config.shadow_bias.unwrap(),
560 shadow_bias_normal: light_config.shadow_bias_normal.unwrap(),
561 },
562 )
563 .ok();
564 }
565 }
567
568 if config.core.auto_add_floor {
570 self.create_floor(config);
571 }
572
573 config.set_consumed();
575 }
576
577 pub fn create_floor(&mut self, config: &Config) {
578 #[allow(clippy::cast_possible_truncation)]
579 #[allow(clippy::cast_sign_loss)]
580 let floor_builder = match config.core.floor_type {
581 FloorType::Solid => Geom::build_plane(
582 config.core.floor_origin.unwrap(),
583 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
584 config.core.floor_scale.unwrap(),
585 config.core.floor_scale.unwrap(),
586 false,
587 ),
588 FloorType::Grid => Geom::build_grid(
589 config.core.floor_origin.unwrap(),
590 na::Vector3::<f32>::new(0.0, 1.0, 0.0),
591 (config.core.floor_scale.unwrap() / 1.0) as u32,
592 (config.core.floor_scale.unwrap() / 1.0) as u32,
593 config.core.floor_scale.unwrap(),
594 config.core.floor_scale.unwrap(),
595 false,
596 ),
597 };
598
599 #[allow(clippy::cast_possible_truncation)]
600 let floor_ent = self
601 .get_or_create_entity(GLOSS_FLOOR_NAME)
602 .insert_builder(floor_builder)
603 .insert(VisMesh {
604 show_mesh: config.core.floor_type == FloorType::Solid,
605 solid_color: na::Vector4::<f32>::new(0.08, 0.08, 0.08, 1.0), perceptual_roughness: 0.70,
608 uv_scale: config.core.floor_uv_scale.unwrap(),
609 color_type: MeshColorType::Texture,
611 ..Default::default()
612 })
613 .insert(VisPoints::default())
614 .insert(VisLines {
615 show_lines: config.core.floor_type == FloorType::Grid,
616 line_color: na::Vector4::<f32>::new(0.2, 0.2, 0.2, 1.0),
617 line_width: config.core.floor_grid_line_width,
618 antialias_edges: true,
619 ..Default::default()
620 })
621 .entity();
622
623 if config.core.floor_texture == FloorTexture::Checkerboard {
624 let texture_checkerboard = DiffuseImg::new_from_buf(
625 CHECKERBOARD_BYTES,
626 &ImgConfig {
627 mipmap_generation_cpu: true, ..Default::default()
630 },
631 );
632 let _ = self.world.insert_one(floor_ent, texture_checkerboard);
633 }
634 }
635
636 #[allow(clippy::missing_panics_doc)]
637 pub fn get_floor(&self) -> Option<Actor> {
638 let ent_opt = self.name2entity.get(GLOSS_FLOOR_NAME);
639 ent_opt.map(|ent| Actor::from_entity(*ent))
640 }
641
642 pub fn has_floor(&self) -> bool {
643 self.name2entity.get(GLOSS_FLOOR_NAME).is_some()
644 }
645
646 pub fn remove_all_gpu_components(&mut self) {
647 let mut command_buffer = CommandBuffer::new();
649 for (entity, ()) in self.world.query::<()>().iter() {
650 command_buffer.remove_one::<VertsGPU>(entity);
651 command_buffer.remove_one::<UVsGPU>(entity);
652 command_buffer.remove_one::<NormalsGPU>(entity);
653 command_buffer.remove_one::<ColorsGPU>(entity);
654 command_buffer.remove_one::<EdgesGPU>(entity);
655 command_buffer.remove_one::<FacesGPU>(entity);
656 command_buffer.remove_one::<TangentsGPU>(entity);
657 command_buffer.remove_one::<DiffuseTex>(entity);
658 command_buffer.remove_one::<NormalTex>(entity);
659 command_buffer.remove_one::<MetalnessTex>(entity);
660 command_buffer.remove_one::<RoughnessTex>(entity);
661 command_buffer.remove_one::<EnvironmentMapGpu>(entity);
662 command_buffer.remove_one::<ShadowMap>(entity);
663 }
664
665 command_buffer.run_on(&mut self.world);
666 }
667}
668
669pub struct EntityMut<'w> {
671 world: &'w mut World,
672 entity: Entity,
673 }
675
676impl<'w> EntityMut<'w> {
678 pub(crate) fn new(world: &'w mut World, entity: Entity) -> Self {
679 EntityMut { world, entity }
680 }
681
682 pub fn insert<T: Component>(&mut self, component: T) -> &mut Self {
685 self.insert_bundle((component,))
686 }
687 pub fn insert_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
690 let _ = self.world.insert(self.entity, bundle);
691 self
692 }
693 pub fn insert_builder(&mut self, mut builder: EntityBuilder) -> &mut Self {
696 let _ = self.world.insert(self.entity, builder.build());
697 self
698 }
699 pub fn get_comp<'a, T: gloss_hecs::ComponentRef<'a>>(&'a self) -> <T as ComponentRef<'a>>::Ref {
703 let comp = self.world.get::<T>(self.entity).unwrap();
704 comp
705 }
706 pub fn entity(&self) -> Entity {
707 self.entity
708 }
709}