gloss_renderer/
scene.rs

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
29// TODO make parametric checkerboard
30static CHECKERBOARD_BYTES: &[u8; 2324] = include_bytes!("../data/checkerboard.png");
31
32/// Scene contains the ECS world and various functionality to interact with it.
33#[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, //defer insertions and deletion of scene entities for whenever we apply this command buffer
40    entity_resource: Entity,           //unique entity that contains resources, so unique componentes
41}
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    /// Gets the entity that contains all the resources
63    pub fn get_entity_resource(&self) -> Entity {
64        self.entity_resource
65    }
66
67    /// Creates a name that is guaranteed to be unused
68    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    /// Creates a entity with a name or gets the one that already exists with
81    /// that concrete name You can keep adding components to this entity
82    /// with .insert()
83    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()),))); //to insert a single component we use a tuple like (x,)
89        EntityMut::new(&mut self.world, *entity_ref)
90    }
91
92    /// Creates a entity with a name or gets the one that already exists with
93    /// that concrete name You can keep adding components to this entity
94    /// with .insert() Also inserts a renderable component
95    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))); //to insert a single component we use a tuple like (x,)
101        EntityMut::new(&mut self.world, *entity_ref)
102    }
103
104    /// Despawns entity with a certain name and all it's components
105    pub fn despawn_with_name(&mut self, name: &str) {
106        //get the entity from the world if there is one, despawn it from the world and
107        // remove it from our internal hashmap
108        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        //if the entity has a name get it so we can remove it form our hashmap of
117        // name2entity
118        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    /// # Panics
126    /// Will panic if no entity has that name
127    pub fn get_entity_with_name(&self, name: &str) -> Option<Entity> {
128        self.name2entity.get(name).copied()
129    }
130
131    /// # Panics
132    /// Will panic if no entity has that name
133    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    /// # Panics
139    /// Will return None if no entity with id found
140    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    /// # Panics
153    /// Will panic if there is no camera added yet
154    pub fn get_current_cam(&self) -> Option<Camera> {
155        // TODO this has to be done better that with just a hard coded name. Maybe a
156        // marker component on the camera?
157        let entity_opt = self.name2entity.get(GLOSS_CAM_NAME);
158        entity_opt.map(|ent| Camera::from_entity(*ent))
159    }
160
161    /// Use to create a unique component, similar to resources in Bevy
162    /// # Panics
163    /// Will panic if the entity that contains all the resources has not been
164    /// created
165    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    /// # Panics
170    /// This function will panic if the entity that contains all the resources
171    /// has not been created.
172    ///
173    /// # Errors
174    /// This function will return an error if the required component does not
175    /// exist.
176    pub fn remove_resource<T: gloss_hecs::Component>(&mut self) -> Result<T, gloss_hecs::ComponentError> {
177        self.world.remove_one::<T>(self.entity_resource) //DO NOT unwrap. this
178                                                         // functions throws
179                                                         // error if the
180                                                         // component was
181                                                         // already removed but
182                                                         // we don't care if we
183                                                         // do repeted
184                                                         // remove_resource
185    }
186
187    /// # Panics
188    /// Will panic if the entity that contains all the resources has not been
189    /// created
190    pub fn has_resource<T: gloss_hecs::Component>(&self) -> bool {
191        self.world.has::<T>(self.entity_resource).unwrap()
192    }
193
194    /// Gets a resource which is a component shared between all entities
195    /// Use with: scene.get_resource::<&mut Component>();
196    /// # Errors
197    /// Will error if the entity that contains all the resources has not been
198    /// created
199    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    /// # Panics
204    /// Will panic if the entity has not been created
205    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    /// Generic function to get a component for a certain entity. Merely
212    /// syntactic sugar. Use with: scene.get_comp::<&mut
213    /// Component>(&entity);
214    ///
215    /// # Errors
216    /// Will error if the component or the entity does not exist
217    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    /// # Panics
225    /// Will panic if the entity does not have a name assigned
226    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        //sort by name
232        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        //sort by name
246        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        //sort by name
260        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    // # Errors
276    // Will return an error if the entity with the given name is not found.
277    // # Panics
278    // Will panic if no entity has that name
279    // pub fn remove_renderable(&mut self, name: &str) -> Result<(), String> {
280    //     let r_name = RString::from(name.to_string());
281    //     if let Some(&entity) = self.name2entity.get(&r_name) {
282    //         self.command_buffer.despawn(entity);
283    //         self.command_buffer.run_on(&mut self.world);
284    //         self.name2entity.remove(&r_name);
285    //         Ok(())
286    //     } else {
287    //         Err(format!("Entity with name '{name}' not found"))
288    //     }
289    // }
290
291    #[allow(clippy::cast_possible_truncation)]
292    pub fn nr_renderables(&self) -> u32 {
293        self.get_renderables(false).len() as u32
294    }
295
296    /// get two points that define the minimal point of the scene in all
297    /// dimensions and the maximum point of the scene in all directions. These
298    /// two points would form a rectangle containing the whole scene without the
299    /// floor
300    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            //Some meshes may not have a model matrix yet because the prepass hasn't run
310            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            //get min and max vertex in obj coords
319            let min_coord_vec: Vec<f32> = verts.0.min_vec();
320            let max_coord_vec: Vec<f32> = verts.0.max_vec();
321            // let min_coord_vec: Vec<f32> = verts.0.column_iter().map(|c|
322            // c.min()).collect(); let max_coord_vec: Vec<f32> =
323            // verts.0.column_iter().map(|c| c.max()).collect();
324            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            //get the points to world coords
328            let min_point_w = model_matrix.0 * min_point;
329            let max_point_w = model_matrix.0 * max_point;
330
331            //get the min/max between these points of this mesh and the global one
332            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            //get min and max vertex in obj coords
354            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    /// get the scale of the scene. useful for adding camera or lights
363    /// automtically
364    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        //get scale as the maximum distance between any of the coordinates
373        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        //exactly the miggle between min and max
391        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            //rest of light that are not in the 3 point light
412            _ => {
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 * 3.5 * scale; //move the light
422        // starting from the center in the direction by a certain amout so that in
423        // engulfs the whole scene
424        let position = centroid + dir_movement * 8.0 * scale; //move the light starting from the center in the direction by a certain amout
425                                                              // so that in engulfs the whole scene
426        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        //core
469        let floor_scale_multiplier = 300.0;
470        if config.core.floor_scale.is_none() {
471            config.core.floor_scale = Some(scale * floor_scale_multiplier); //just make a realy large plane. At some point we should change it to be actualyl infinite
472        }
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        //camera
481        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; //far plane can be quite big. The near plane shouldn't be too tiny because it
494                                                           // make the depth have very little precision
495            config.scene.cam.far = Some(far);
496        }
497
498        //render
499        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        //lights
510        // let three_point_lights = self.create_3_point_light_configs(scale, centroid);
511        for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
512            self.init_3_point_light(light_config, idx, scale, &centroid);
513        }
514
515        //specify that it is now concrete so we don't rerun this function
516        config.set_concrete();
517    }
518
519    /// # Panics
520    /// Will panic if the camera entity has not yet been created
521    #[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        //camera=============
526        let cam = self.get_current_cam().expect("Camera should be created");
527        //add a pos lookat if there is none
528        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        //add a projection if there is none
544        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        //create lights
561        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, //radians
568                    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            //shadow
581            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            // .insert(ShadowCaster { shadow_res: 2048 });
596        }
597
598        //floor
599        if config.core.auto_add_floor {
600            self.create_floor(config);
601        }
602
603        //specify that it is now consumed so we don't rerun this function again
604        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), //106
636                // perceptual_roughness: 0.75,
637                perceptual_roughness: 0.70,
638                uv_scale: config.core.floor_uv_scale.unwrap(),
639                // metalness: 1.0,
640                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, /* we keep the generation of mipmaps on cpu because GPU one doesn't run on wasm due to webgl2 not
658                                                  * allowing reading and writing from the same texture even if it's different mipmaps :( */
659                    ..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        //TODO this is very brittle, need to somehow mark the gpu components somehow
678        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
699/// A mutable reference to a particular [`Entity`] and all of its components
700pub struct EntityMut<'w> {
701    world: &'w mut World,
702    entity: Entity,
703    // location: EntityLocation,
704}
705
706//similar to bevys entitymut https://docs.rs/bevy_ecs/latest/src/bevy_ecs/world/entity_ref.rs.html#183
707impl<'w> EntityMut<'w> {
708    pub(crate) fn new(world: &'w mut World, entity: Entity) -> Self {
709        EntityMut { world, entity }
710    }
711
712    /// Inserts a component to this entity.
713    /// This will overwrite any previous value(s) of the same component type.
714    pub fn insert<T: Component>(&mut self, component: T) -> &mut Self {
715        self.insert_bundle((component,))
716    }
717    /// Inserts a [`Bundle`] of components to the entity.
718    /// This will overwrite any previous value(s) of the same component type.
719    pub fn insert_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
720        let _ = self.world.insert(self.entity, bundle);
721        self
722    }
723    /// Inserts a [`EntityBuilder`] of components to the entity.
724    /// This will overwrite any previous value(s) of the same component type.
725    pub fn insert_builder(&mut self, mut builder: EntityBuilder) -> &mut Self {
726        let _ = self.world.insert(self.entity, builder.build());
727        self
728    }
729    /// Convenience function to get a component. Mostly useful for the python
730    /// bindings. # Panics
731    /// Will panic if the entity does not exist in the world
732    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}