gloss_renderer/
scene.rs

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
27// TODO make parametric checkerboard
28static CHECKERBOARD_BYTES: &[u8; 2324] = include_bytes!("../data/checkerboard.png");
29
30/// Scene contains the ECS world and various functionality to interact with it.
31#[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, //defer insertions and deletion of scene entities for whenever we apply this command buffer
38    entity_resource: Entity,           //unique entity that contains resources, so unique componentes
39}
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    /// Gets the entity that contains all the resources
61    pub fn get_entity_resource(&self) -> Entity {
62        self.entity_resource
63    }
64
65    /// Creates a name that is guaranteed to be unused
66    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    /// Creates a entity with a name or gets the one that already exists with
79    /// that concrete name You can keep adding components to this entity
80    /// with .insert()
81    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()),))); //to insert a single component we use a tuple like (x,)
87        EntityMut::new(&mut self.world, *entity_ref)
88    }
89
90    /// Creates a entity with a name or gets the one that already exists with
91    /// that concrete name You can keep adding components to this entity
92    /// with .insert() Also inserts a renderable component
93    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))); //to insert a single component we use a tuple like (x,)
99        EntityMut::new(&mut self.world, *entity_ref)
100    }
101
102    /// Despawns entity with a certain name and all it's components
103    pub fn despawn_with_name(&mut self, name: &str) {
104        //get the entity from the world if there is one, despawn it from the world and
105        // remove it from our internal hashmap
106        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        //if the entity has a name get it so we can remove it form our hashmap of
115        // name2entity
116        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    /// # Panics
124    /// Will panic if no entity has that name
125    pub fn get_entity_with_name(&self, name: &str) -> Option<Entity> {
126        self.name2entity.get(name).copied()
127    }
128
129    /// # Panics
130    /// Will panic if no entity has that name
131    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    /// # Panics
137    /// Will panic if there is no camera added yet
138    pub fn get_current_cam(&self) -> Option<Camera> {
139        // TODO this has to be done better that with just a hard coded name. Maybe a
140        // marker component on the camera?
141        let entity_opt = self.name2entity.get(GLOSS_CAM_NAME);
142        entity_opt.map(|ent| Camera::from_entity(*ent))
143    }
144
145    /// Use to create a unique component, similar to resources in Bevy
146    /// # Panics
147    /// Will panic if the entity that contains all the resources has not been
148    /// created
149    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    /// # Panics
154    /// This function will panic if the entity that contains all the resources
155    /// has not been created.
156    ///
157    /// # Errors
158    /// This function will return an error if the required component does not
159    /// exist.
160    pub fn remove_resource<T: gloss_hecs::Component>(&mut self) -> Result<T, gloss_hecs::ComponentError> {
161        self.world.remove_one::<T>(self.entity_resource) //DO NOT unwrap. this
162                                                         // functions throws
163                                                         // error if the
164                                                         // component was
165                                                         // already removed but
166                                                         // we don't care if we
167                                                         // do repeted
168                                                         // remove_resource
169    }
170
171    /// # Panics
172    /// Will panic if the entity that contains all the resources has not been
173    /// created
174    pub fn has_resource<T: gloss_hecs::Component>(&self) -> bool {
175        self.world.has::<T>(self.entity_resource).unwrap()
176    }
177
178    /// Gets a resource which is a component shared between all entities
179    /// Use with: scene.get_resource::<&mut Component>();
180    /// # Errors
181    /// Will error if the entity that contains all the resources has not been
182    /// created
183    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    /// # Panics
188    /// Will panic if the entity has not been created
189    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    /// Generic function to get a component for a certain entity. Merely
196    /// syntactic sugar. Use with: scene.get_comp::<&mut
197    /// Component>(&entity);
198    ///
199    /// # Errors
200    /// Will error if the component or the entity does not exist
201    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    /// # Panics
209    /// Will panic if the entity does not have a name assigned
210    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        //sort by name
216        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        //sort by name
230        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    /// # Errors
246    /// Will return an error if the entity with the given name is not found.
247    /// # Panics
248    /// Will panic if no entity has that name
249    // pub fn remove_renderable(&mut self, name: &str) -> Result<(), String> {
250    //     let r_name = RString::from(name.to_string());
251    //     if let Some(&entity) = self.name2entity.get(&r_name) {
252    //         self.command_buffer.despawn(entity);
253    //         self.command_buffer.run_on(&mut self.world);
254    //         self.name2entity.remove(&r_name);
255    //         Ok(())
256    //     } else {
257    //         Err(format!("Entity with name '{name}' not found"))
258    //     }
259    // }
260
261    #[allow(clippy::cast_possible_truncation)]
262    pub fn nr_renderables(&self) -> u32 {
263        self.get_renderables(false).len() as u32
264    }
265
266    /// get two points that define the minimal point of the scene in all
267    /// dimensions and the maximum point of the scene in all directions. These
268    /// two points would form a rectangle containing the whole scene without the
269    /// floor
270    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            //Some meshes may not have a model matrix yet because the prepass hasn't run
280            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            //get min and max vertex in obj coords
289            let min_coord_vec: Vec<f32> = verts.0.min_vec();
290            let max_coord_vec: Vec<f32> = verts.0.max_vec();
291            // let min_coord_vec: Vec<f32> = verts.0.column_iter().map(|c|
292            // c.min()).collect(); let max_coord_vec: Vec<f32> =
293            // verts.0.column_iter().map(|c| c.max()).collect();
294            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            //get the points to world coords
298            let min_point_w = model_matrix.0 * min_point;
299            let max_point_w = model_matrix.0 * max_point;
300
301            //get the min/max between these points of this mesh and the global one
302            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            //get min and max vertex in obj coords
324            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    /// get the scale of the scene. useful for adding camera or lights
333    /// automtically
334    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        //get scale as the maximum distance between any of the coordinates
343        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        //exactly the miggle between min and max
361        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            //rest of light that are not in the 3 point light
382            _ => {
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 * 3.5 * scale; //move the light
392        // starting from the center in the direction by a certain amout so that in
393        // engulfs the whole scene
394        let position = centroid + dir_movement * 8.0 * scale; //move the light starting from the center in the direction by a certain amout
395                                                              // so that in engulfs the whole scene
396        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        //core
439        let floor_scale_multiplier = 300.0;
440        if config.core.floor_scale.is_none() {
441            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
442        }
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        //camera
451        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; //far plane can be quite big. The near plane shouldn't be too tiny because it
464                                                           // make the depth have very little precision
465            config.scene.cam.far = Some(far);
466        }
467
468        //render
469        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        //lights
480        // let three_point_lights = self.create_3_point_light_configs(scale, centroid);
481        for (idx, light_config) in config.scene.lights.iter_mut().enumerate() {
482            self.init_3_point_light(light_config, idx, scale, &centroid);
483        }
484
485        //specify that it is now concrete so we don't rerun this function
486        config.set_concrete();
487    }
488
489    /// # Panics
490    /// Will panic if the camera entity has not yet been created
491    #[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        //camera=============
496        let cam = self.get_current_cam().expect("Camera should be created");
497        //add a pos lookat if there is none
498        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        //add a projection if there is none
514        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        //create lights
531        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, //radians
538                    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            //shadow
551            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            // .insert(ShadowCaster { shadow_res: 2048 });
566        }
567
568        //floor
569        if config.core.auto_add_floor {
570            self.create_floor(config);
571        }
572
573        //specify that it is now consumed so we don't rerun this function again
574        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), //106
606                // perceptual_roughness: 0.75,
607                perceptual_roughness: 0.70,
608                uv_scale: config.core.floor_uv_scale.unwrap(),
609                // metalness: 1.0,
610                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, /* we keep the generation of mipmaps on cpu because GPU one doesn't run on wasm due to webgl2 not
628                                                  * allowing reading and writing from the same texture even if it's different mipmaps :( */
629                    ..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        //TODO this is very brittle, need to somehow mark the gpu components somehow
648        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
669/// A mutable reference to a particular [`Entity`] and all of its components
670pub struct EntityMut<'w> {
671    world: &'w mut World,
672    entity: Entity,
673    // location: EntityLocation,
674}
675
676//similar to bevys entitymut https://docs.rs/bevy_ecs/latest/src/bevy_ecs/world/entity_ref.rs.html#183
677impl<'w> EntityMut<'w> {
678    pub(crate) fn new(world: &'w mut World, entity: Entity) -> Self {
679        EntityMut { world, entity }
680    }
681
682    /// Inserts a component to this entity.
683    /// This will overwrite any previous value(s) of the same component type.
684    pub fn insert<T: Component>(&mut self, component: T) -> &mut Self {
685        self.insert_bundle((component,))
686    }
687    /// Inserts a [`Bundle`] of components to the entity.
688    /// This will overwrite any previous value(s) of the same component type.
689    pub fn insert_bundle(&mut self, bundle: impl DynamicBundle) -> &mut Self {
690        let _ = self.world.insert(self.entity, bundle);
691        self
692    }
693    /// Inserts a [`EntityBuilder`] of components to the entity.
694    /// This will overwrite any previous value(s) of the same component type.
695    pub fn insert_builder(&mut self, mut builder: EntityBuilder) -> &mut Self {
696        let _ = self.world.insert(self.entity, builder.build());
697        self
698    }
699    /// Convenience function to get a component. Mostly useful for the python
700    /// bindings. # Panics
701    /// Will panic if the entity does not exist in the world
702    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}