gloss_renderer/components/
mesh_cpu_comps.rs

1//! These represent CPU components which you usually add to entities when you
2//! spawn them.
3
4extern crate nalgebra as na;
5// use burn::backend::ndarray::NdArrayDevice;
6// use burn::backend::candle::CandleDevice;
7// use burn::backend::Candle;
8use gloss_img::DynImage;
9use gloss_utils::{
10    io::FileLoader,
11    tensor::{DynamicTensorFloat2D, DynamicTensorInt2D},
12};
13use image::ImageReader;
14use log::warn;
15use na::DMatrix;
16use std::io::{BufReader, Cursor, Read, Seek};
17/// Component that modifications to the config
18#[derive(Clone)]
19pub struct ConfigChanges {
20    pub new_distance_fade_center: na::Point3<f32>,
21}
22
23/// Defines the color type an entity which is displayed as a point cloud
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum PointColorType {
26    Solid = 0,
27    PerVert,
28}
29
30/// Defines the color type an entity which is displayed as a point cloud
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub enum LineColorType {
33    Solid = 0,
34    PerVert,
35}
36
37/// Defines the color type an entity which is displayed as a mesh
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub enum MeshColorType {
40    Solid = 0,
41    PerVert,
42    Texture,
43    UV,
44    Normal,
45    NormalViewCoords,
46}
47
48/// Component for visualization options of lines
49#[derive(Clone)]
50#[allow(clippy::struct_excessive_bools)]
51pub struct VisLines {
52    pub show_lines: bool,
53    pub line_color: na::Vector4<f32>,
54    pub line_width: f32,
55    pub color_type: LineColorType,
56    pub zbuffer: bool,
57    pub antialias_edges: bool,
58    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
59    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
60    // component manually then we shouldn't override his VisMesh.colortype.
61    pub added_automatically: bool,
62}
63/// Component for visualization options of wireframe
64#[derive(Clone)]
65pub struct VisWireframe {
66    pub show_wireframe: bool,
67    pub wire_color: na::Vector4<f32>,
68    pub wire_width: f32,
69    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
70    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
71    // component manually then we shouldn't override his VisMesh.colortype.
72    pub added_automatically: bool,
73}
74/// Component for visualization options of normals
75#[derive(Clone)]
76pub struct VisNormals {
77    pub show_normals: bool,
78    pub normals_color: na::Vector4<f32>,
79    pub normals_width: f32,
80    pub normals_scale: f32, //the scale of the arrows for the normal.
81    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
82    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
83    // component manually then we shouldn't override his VisMesh.colortype.
84    pub added_automatically: bool,
85}
86/// Component for visualization options of point clouds
87#[derive(Clone)]
88#[allow(clippy::struct_excessive_bools)]
89pub struct VisPoints {
90    pub show_points: bool,
91    pub show_points_indices: bool,
92    pub point_color: na::Vector4<f32>,
93    pub point_size: f32,
94    pub is_point_size_in_world_space: bool,
95    pub color_type: PointColorType,
96    pub zbuffer: bool,
97    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
98    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
99    // component manually then we shouldn't override his VisMesh.colortype.
100    pub added_automatically: bool,
101}
102/// Component for visualization options of meshes
103#[derive(Clone)]
104pub struct VisMesh {
105    pub show_mesh: bool,
106    pub solid_color: na::Vector4<f32>,
107    pub metalness: f32,
108    pub perceptual_roughness: f32,
109    pub roughness_black_lvl: f32,
110    pub uv_scale: f32,
111    pub opacity: f32,
112    pub needs_sss: bool,
113    pub color_type: MeshColorType,
114    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
115    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
116    // component manually then we shouldn't override his VisMesh.colortype.
117    pub added_automatically: bool,
118}
119/// Component for visualization options of meshes
120#[derive(Clone)]
121pub struct VisOutline {
122    pub show_outline: bool,
123    pub outline_color: na::Vector4<f32>,
124    pub outline_width: f32,
125    //if this components was added automatically by the renderer this will be set to true. This is useful to know since when we upload textures to
126    // gpu the vis_mesh.color_type will be set to Texture but this should happen ONLY if the VisMesh was added automatically. If the used adds this
127    // component manually then we shouldn't override his VisMesh.colortype.
128    pub added_automatically: bool,
129}
130//implementations of default vis options
131impl Default for VisLines {
132    fn default() -> VisLines {
133        VisLines {
134            show_lines: false,
135            line_color: na::Vector4::<f32>::new(1.0, 0.1, 0.1, 1.0),
136            line_width: 1.0,
137            color_type: LineColorType::Solid,
138            zbuffer: true,
139            antialias_edges: false,
140            added_automatically: false,
141        }
142    }
143}
144impl Default for VisWireframe {
145    fn default() -> VisWireframe {
146        VisWireframe {
147            show_wireframe: false,
148            wire_color: na::Vector4::<f32>::new(1.0, 0.0, 0.0, 1.0),
149            wire_width: 1.0,
150            added_automatically: false,
151        }
152    }
153}
154impl Default for VisNormals {
155    fn default() -> VisNormals {
156        VisNormals {
157            show_normals: false,
158            normals_color: na::Vector4::<f32>::new(1.0, 0.0, 0.0, 1.0),
159            normals_width: 1.0,
160            normals_scale: 1.0,
161            added_automatically: false,
162        }
163    }
164}
165impl Default for VisPoints {
166    fn default() -> VisPoints {
167        VisPoints {
168            show_points: false,
169            show_points_indices: false,
170            point_color: na::Vector4::<f32>::new(245.0 / 255.0, 175.0 / 255.0, 110.0 / 255.0, 1.0),
171            point_size: 1.0,
172            is_point_size_in_world_space: false,
173            color_type: PointColorType::Solid,
174            zbuffer: true,
175            added_automatically: false,
176        }
177    }
178}
179impl Default for VisMesh {
180    fn default() -> VisMesh {
181        VisMesh {
182            show_mesh: true,
183            solid_color: na::Vector4::<f32>::new(1.0, 206.0 / 255.0, 143.0 / 255.0, 1.0),
184            metalness: 0.0,
185            perceptual_roughness: 0.5,
186            roughness_black_lvl: 0.0,
187            uv_scale: 1.0,
188            opacity: 1.0,
189            needs_sss: false,
190            color_type: MeshColorType::Solid,
191            added_automatically: false,
192        }
193    }
194}
195impl Default for VisOutline {
196    fn default() -> VisOutline {
197        VisOutline {
198            show_outline: false,
199            outline_color: na::Vector4::<f32>::new(0.29, 0.82, 0.73, 1.0), // Blue-green default color
200            outline_width: 5.0,
201            added_automatically: false,
202        }
203    }
204}
205
206/// Component that transforms from object coordinates to world. Usually added
207/// automatically but you can also add it yourself.
208#[derive(Clone)]
209pub struct ModelMatrix(pub na::SimilarityMatrix3<f32>); //transform from object coordinates to world corresponds to TfWorldObj
210                                                        // pub struct ModelMatrix(pub na::Affine3<f32>); //transform from object
211                                                        // coordinates to world corresponds to TfWorldObj
212impl Default for ModelMatrix {
213    fn default() -> ModelMatrix {
214        ModelMatrix(na::SimilarityMatrix3::<f32>::identity())
215    }
216}
217impl ModelMatrix {
218    #[must_use]
219    pub fn with_translation(self, t: &na::Vector3<f32>) -> Self {
220        let mut mat = self;
221        mat.0.append_translation_mut(&na::Translation3::new(t[0], t[1], t[2]));
222        mat
223    }
224    #[must_use]
225    pub fn with_rotation_rot3(self, r: &na::Rotation3<f32>) -> Self {
226        let mut mat = self;
227        mat.0.append_rotation_mut(r);
228        mat
229    }
230    #[must_use]
231    pub fn with_rotation_axis_angle(self, v: &na::Vector3<f32>) -> Self {
232        let mut mat = self;
233        mat.0
234            .append_rotation_mut(&na::Rotation3::from_axis_angle(&na::UnitVector3::<f32>::new_normalize(*v), v.norm()));
235        mat
236    }
237    #[must_use]
238    pub fn with_rotation_euler(self, e: &na::Vector3<f32>) -> Self {
239        let mut mat = self;
240        mat.0.append_rotation_mut(&na::Rotation3::from_euler_angles(e.x, e.y, e.z));
241        mat
242    }
243    #[must_use]
244    pub fn interpolate(&self, other: &Self, other_weight: f32) -> Self {
245        if !(0.0..=1.0).contains(&other_weight) {
246            warn!("pose interpolation weight is outside the [0,1] range, will clamp. Weight is {other_weight}");
247        }
248        let other_weight = other_weight.clamp(0.0, 1.0);
249
250        let lerp_isometry = self.0.isometry.lerp_slerp(&other.0.isometry, other_weight);
251        let lerp_scaling = self.0.scaling() * (1.0 - other_weight) + other.0.scaling() * other_weight;
252
253        let lerp_similarity = na::SimilarityMatrix3::from_parts(lerp_isometry.translation, lerp_isometry.rotation, lerp_scaling);
254
255        ModelMatrix(lerp_similarity)
256    }
257}
258
259/// Component that represents a track of camera extrinsics - num frames x 16
260#[derive(Clone, Debug)]
261pub struct CamTrack(pub DMatrix<f32>);
262
263#[derive(Clone, Debug)]
264pub struct Verts(pub DynamicTensorFloat2D);
265
266/// Component that represents a matrix of vertex positions for the first vertex
267/// of an edge
268#[derive(Clone, Debug)]
269pub struct EdgesV1(pub DynamicTensorFloat2D);
270/// Component that represents a matrix of vertex positions for the second vertex
271/// of an edge
272#[derive(Clone, Debug)]
273pub struct EdgesV2(pub DynamicTensorFloat2D);
274
275/// Component that represents a matrix of face indices for rendering a triangle
276/// mesh
277// #[derive(Clone)]
278// pub struct Faces(pub DMatrix<u32>);
279#[derive(Clone)]
280pub struct Faces(pub DynamicTensorInt2D);
281
282/// Component that represents a matrix of edges as Nx2 where each row is the
283/// start idx and end idx of an edge which indexes into [``Verts``]
284#[derive(Clone, Debug)]
285pub struct Edges(pub DynamicTensorInt2D);
286
287/// Component that represents UV coordinates
288#[derive(Clone)]
289pub struct UVs(pub DynamicTensorFloat2D);
290// / Component that represents the scale of the uv coordinates. Increasing it
291// will tile the texture on the mesh. #[derive(Clone)]
292// pub struct UvScale(pub f32);
293
294/// Component that represents normal vectors
295#[derive(Clone)]
296pub struct Normals(pub DynamicTensorFloat2D);
297
298/// Component that represents tangents
299#[derive(Clone)]
300pub struct Tangents(pub DynamicTensorFloat2D);
301
302/// Component that represents per vertex colors
303#[derive(Clone)]
304pub struct Colors(pub DynamicTensorFloat2D);
305
306#[derive(Clone)]
307#[allow(clippy::struct_excessive_bools)]
308pub struct ImgConfig {
309    pub keep_on_cpu: bool,
310    pub fast_upload: bool, //fast upload uses the staging memory of wgpu but potentially uses more memory
311    pub generate_mipmaps: bool,
312    pub mipmap_generation_cpu: bool,
313}
314impl Default for ImgConfig {
315    fn default() -> Self {
316        Self {
317            keep_on_cpu: true,
318            fast_upload: true,
319            generate_mipmaps: true,
320            mipmap_generation_cpu: false,
321        }
322    }
323}
324
325/// A generic Img that has a path and img
326/// Concrete images like [``DiffuseImg``] will contain the generic img
327#[derive(Clone)]
328pub struct GenericImg {
329    pub path: Option<String>,
330    pub cpu_img: Option<DynImage>, //keep it as an option so we can drop it and release the memory
331    pub config: ImgConfig,
332}
333impl GenericImg {
334    /// # Panics
335    /// Will panic if the path cannot be opened.
336    pub fn new_from_path(path: &str, config: &ImgConfig) -> Self {
337        let cpu_img = Some(ImageReader::open(path).unwrap().decode().unwrap());
338
339        Self {
340            path: Some(path.to_string()),
341            cpu_img: cpu_img.map(|v| v.try_into().unwrap()),
342            config: config.clone(),
343        }
344    }
345
346    /// # Panics
347    /// Will panic if the path cannot be opened.
348    pub async fn new_from_path_async(path: &str, config: &ImgConfig) -> Self {
349        let reader = ImageReader::new(BufReader::new(FileLoader::open(path).await))
350            .with_guessed_format()
351            .expect("Cursor io never fails");
352
353        let cpu_img = Some(reader.decode().unwrap());
354
355        Self {
356            path: Some(path.to_string()),
357            cpu_img: cpu_img.map(|v| v.try_into().unwrap()),
358            config: config.clone(),
359        }
360    }
361
362    /// # Panics
363    /// Will panic if the img cannot be processed and decoded.
364    pub fn new_from_buf(buf: &[u8], config: &ImgConfig) -> Self {
365        Self::new_from_reader(Cursor::new(buf), config)
366    }
367
368    /// # Panics
369    /// Will panic if the img cannot be processed and decoded.
370    pub fn new_from_reader<R: Read + Seek>(reader: R, config: &ImgConfig) -> Self {
371        let reader_img = ImageReader::new(BufReader::new(reader))
372            .with_guessed_format()
373            .expect("Format for image should be something known and valid");
374
375        let cpu_img = Some(reader_img.decode().unwrap());
376
377        Self {
378            path: None,
379            cpu_img: cpu_img.map(|v| v.try_into().unwrap()),
380            config: config.clone(),
381        }
382    }
383
384    /// Creates a `GenericImg` from raw pixel data
385    /// # Panics
386    /// Will panic if the image cannot be created from the raw pixel data or if an invalid number of channels is provided.
387    pub fn new_from_raw_pixels(pixels: Vec<u8>, width: u32, height: u32, channels: u8, config: &ImgConfig) -> Self {
388        use image::{DynamicImage, GrayAlphaImage, GrayImage, RgbImage, RgbaImage};
389
390        let cpu_img = match channels {
391            1 => {
392                let gray_img = GrayImage::from_raw(width, height, pixels).expect("Failed to create grayscale image from raw pixels");
393                Some(DynamicImage::ImageLuma8(gray_img))
394            }
395            2 => {
396                let gray_alpha_img = GrayAlphaImage::from_raw(width, height, pixels).expect("Failed to create grayscale+alpha image from raw pixels");
397                Some(DynamicImage::ImageLumaA8(gray_alpha_img))
398            }
399            3 => {
400                let rgb_img = RgbImage::from_raw(width, height, pixels).expect("Failed to create RGB image from raw pixels");
401                Some(DynamicImage::ImageRgb8(rgb_img))
402            }
403            4 => {
404                let rgba_img = RgbaImage::from_raw(width, height, pixels).expect("Failed to create RGBA image from raw pixels");
405                Some(DynamicImage::ImageRgba8(rgba_img))
406            }
407            _ => panic!("Unsupported number of channels: {channels}. Supported: 1 (Luma), 2 (LumaA), 3 (RGB), 4 (RGBA)"),
408        };
409
410        Self {
411            path: None,
412            cpu_img: cpu_img.map(|v| v.try_into().unwrap()),
413            config: config.clone(),
414        }
415    }
416
417    pub fn img_ref(&self) -> &DynImage {
418        self.cpu_img.as_ref().unwrap()
419    }
420
421    pub fn img_ref_mut(&mut self) -> &mut DynImage {
422        self.cpu_img.as_mut().unwrap()
423    }
424}
425
426/// Component which represents a diffuse img.
427#[derive(Clone)]
428pub struct DiffuseImg {
429    pub generic_img: GenericImg,
430}
431
432impl DiffuseImg {
433    pub fn new_from_path(path: &str, config: &ImgConfig) -> Self {
434        let generic_img = GenericImg::new_from_path(path, config);
435        Self { generic_img }
436    }
437
438    pub async fn new_from_path_async(path: &str, config: &ImgConfig) -> Self {
439        let generic_img = GenericImg::new_from_path_async(path, config).await;
440        Self { generic_img }
441    }
442
443    pub fn new_from_buf(buf: &[u8], config: &ImgConfig) -> Self {
444        let generic_img = GenericImg::new_from_buf(buf, config);
445        Self { generic_img }
446    }
447
448    pub fn new_from_reader<R: Read + Seek>(reader: R, config: &ImgConfig) -> Self {
449        let generic_img = GenericImg::new_from_reader(reader, config);
450        Self { generic_img }
451    }
452
453    pub fn new_from_raw_pixels(pixels: Vec<u8>, width: u32, height: u32, channels: u8, config: &ImgConfig) -> Self {
454        let generic_img = GenericImg::new_from_raw_pixels(pixels, width, height, channels, config);
455        Self { generic_img }
456    }
457}
458
459/// Component which represents a normal img.
460#[derive(Clone)]
461pub struct NormalImg {
462    pub generic_img: GenericImg,
463}
464
465impl NormalImg {
466    pub fn new_from_path(path: &str, config: &ImgConfig) -> Self {
467        let generic_img = GenericImg::new_from_path(path, config);
468        Self { generic_img }
469    }
470
471    pub async fn new_from_path_async(path: &str, config: &ImgConfig) -> Self {
472        let generic_img = GenericImg::new_from_path_async(path, config).await;
473        Self { generic_img }
474    }
475
476    pub fn new_from_buf(buf: &[u8], config: &ImgConfig) -> Self {
477        let generic_img = GenericImg::new_from_buf(buf, config);
478        Self { generic_img }
479    }
480
481    pub fn new_from_reader<R: Read + Seek>(reader: R, config: &ImgConfig) -> Self {
482        let generic_img = GenericImg::new_from_reader(reader, config);
483        Self { generic_img }
484    }
485
486    pub fn new_from_raw_pixels(pixels: Vec<u8>, width: u32, height: u32, channels: u8, config: &ImgConfig) -> Self {
487        let generic_img = GenericImg::new_from_raw_pixels(pixels, width, height, channels, config);
488        Self { generic_img }
489    }
490}
491
492/// Component which represents a metalness img.
493pub struct MetalnessImg {
494    pub generic_img: GenericImg,
495}
496impl MetalnessImg {
497    pub fn new_from_path(path: &str, config: &ImgConfig) -> Self {
498        let generic_img = GenericImg::new_from_path(path, config);
499        Self { generic_img }
500    }
501
502    pub async fn new_from_path_async(path: &str, config: &ImgConfig) -> Self {
503        let generic_img = GenericImg::new_from_path_async(path, config).await;
504        Self { generic_img }
505    }
506
507    pub fn new_from_buf(buf: &[u8], config: &ImgConfig) -> Self {
508        let generic_img = GenericImg::new_from_buf(buf, config);
509        Self { generic_img }
510    }
511
512    pub fn new_from_reader<R: Read + Seek>(reader: R, config: &ImgConfig) -> Self {
513        let generic_img = GenericImg::new_from_reader(reader, config);
514        Self { generic_img }
515    }
516
517    pub fn new_from_raw_pixels(pixels: Vec<u8>, width: u32, height: u32, channels: u8, config: &ImgConfig) -> Self {
518        let generic_img = GenericImg::new_from_raw_pixels(pixels, width, height, channels, config);
519        Self { generic_img }
520    }
521}
522
523/// Component which represents a roughness img. Assumes it is stored as
524/// perceptual roughness/
525#[derive(Clone)]
526pub struct RoughnessImg {
527    pub generic_img: GenericImg,
528}
529impl RoughnessImg {
530    pub fn new_from_path(path: &str, config: &ImgConfig) -> Self {
531        let generic_img = GenericImg::new_from_path(path, config);
532        Self { generic_img }
533    }
534
535    pub async fn new_from_path_async(path: &str, config: &ImgConfig) -> Self {
536        let generic_img = GenericImg::new_from_path_async(path, config).await;
537        Self { generic_img }
538    }
539
540    pub fn new_from_buf(buf: &[u8], config: &ImgConfig) -> Self {
541        let generic_img = GenericImg::new_from_buf(buf, config);
542        Self { generic_img }
543    }
544
545    pub fn new_from_reader<R: Read + Seek>(reader: R, config: &ImgConfig) -> Self {
546        let generic_img = GenericImg::new_from_reader(reader, config);
547        Self { generic_img }
548    }
549
550    pub fn new_from_raw_pixels(pixels: Vec<u8>, width: u32, height: u32, channels: u8, config: &ImgConfig) -> Self {
551        let generic_img = GenericImg::new_from_raw_pixels(pixels, width, height, channels, config);
552        Self { generic_img }
553    }
554}
555
556pub trait GenericImageGetter {
557    fn generic_img(&self) -> &GenericImg;
558    fn generic_img_mut(&mut self) -> &mut GenericImg;
559}
560
561impl GenericImageGetter for DiffuseImg {
562    fn generic_img(&self) -> &GenericImg {
563        &self.generic_img
564    }
565    fn generic_img_mut(&mut self) -> &mut GenericImg {
566        &mut self.generic_img
567    }
568}
569
570impl GenericImageGetter for NormalImg {
571    fn generic_img(&self) -> &GenericImg {
572        &self.generic_img
573    }
574    fn generic_img_mut(&mut self) -> &mut GenericImg {
575        &mut self.generic_img
576    }
577}
578
579impl GenericImageGetter for RoughnessImg {
580    fn generic_img(&self) -> &GenericImg {
581        &self.generic_img
582    }
583    fn generic_img_mut(&mut self) -> &mut GenericImg {
584        &mut self.generic_img
585    }
586}
587
588impl GenericImageGetter for MetalnessImg {
589    fn generic_img(&self) -> &GenericImg {
590        &self.generic_img
591    }
592    fn generic_img_mut(&mut self) -> &mut GenericImg {
593        &mut self.generic_img
594    }
595}
596
597//implement some atributes for the vertex atributes so we can use them in a
598// generic function also implement common things like the atom size of an
599// element which is f32 for most or u32 for faces and edges https://stackoverflow.com/a/53085395
600//https://stackoverflow.com/a/66794115
601// pub trait CpuAtrib<T> {
602//     // type TypeElement;
603//     fn byte_size_element(&self) -> usize;
604//     // pub fn get_data(&self) -> DMatrix<Self::TypeElement>;
605//     fn data_ref(&self) -> &DMatrix<T>;
606// }
607
608// impl CpuAtrib<f32> for Colors {
609//     fn byte_size_element(&self) -> usize {
610//         std::mem::size_of::<f32>()
611//     }
612//     fn data_ref(&self) -> &DMatrix<f32> {
613//         &self.0
614//     }
615// }
616
617/// Environment map based ambient lighting representing light from distant
618/// scenery.
619///
620/// When added as a resource to the scene, this component adds indirect light
621/// to every point of the scene (including inside, enclosed areas) based on
622/// an environment cubemap texture. This is similar to [`crate::AmbientLight`],
623/// but higher quality, and is intended for outdoor scenes.
624///
625/// The environment map must be prefiltered into a diffuse and specular cubemap
626/// based on the [split-sum approximation](https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf).
627///
628/// To prefilter your environment map, you can use `KhronosGroup`'s [glTF-IBL-Sampler](https://github.com/KhronosGroup/glTF-IBL-Sampler).
629/// The diffuse map uses the Lambertian distribution, and the specular map uses
630/// the GGX distribution.
631///
632/// `KhronosGroup` also has several prefiltered environment maps that can be found [here](https://github.com/KhronosGroup/glTF-Sample-Environments)
633pub struct EnvironmentMap {
634    pub diffuse_path: String,
635    pub specular_path: String,
636}
637impl EnvironmentMap {
638    pub fn new_from_path(diffuse_path: &str, specular_path: &str) -> Self {
639        Self {
640            diffuse_path: String::from(diffuse_path),
641            specular_path: String::from(specular_path),
642        }
643    }
644}
645
646/// Component for storing approximate bounding vertices around an object.
647/// Useful for computing scale of objects created on GPU directly which have only `VertsGPU` and no Verts component
648#[derive(Clone)]
649pub struct BoundingBox {
650    pub min: na::Point3<f32>,
651    pub max: na::Point3<f32>,
652}
653impl BoundingBox {
654    pub fn new(min: na::Point3<f32>, max: na::Point3<f32>) -> Self {
655        Self { min, max }
656    }
657    pub fn from_center_and_scale(center: &na::Point3<f32>, scale: &na::Vector3<f32>) -> Self {
658        let half_scale = scale / 2.0;
659        Self {
660            min: na::Point3::from(center.coords - half_scale),
661            max: na::Point3::from(center.coords + half_scale),
662        }
663    }
664    pub fn center(&self) -> na::Point3<f32> {
665        na::Point3::from((self.min.coords + self.max.coords) / 2.0)
666    }
667    pub fn size(&self) -> na::Vector3<f32> {
668        self.max.coords - self.min.coords
669    }
670    pub fn diagonal_length(&self) -> f32 {
671        (self.max.coords - self.min.coords).norm()
672    }
673}
674
675// /so we can use the Components inside the Mutex<Hashmap> in the scene and wasm
676// https://stackoverflow.com/a/73773940/22166964
677// shenanigans
678//ConfigDeltas
679#[cfg(target_arch = "wasm32")]
680unsafe impl Send for ConfigChanges {}
681#[cfg(target_arch = "wasm32")]
682unsafe impl Sync for ConfigChanges {}
683//verts
684#[cfg(target_arch = "wasm32")]
685unsafe impl Send for Verts {}
686#[cfg(target_arch = "wasm32")]
687unsafe impl Sync for Verts {}
688//vertse1
689#[cfg(target_arch = "wasm32")]
690unsafe impl Send for EdgesV1 {}
691#[cfg(target_arch = "wasm32")]
692unsafe impl Sync for EdgesV1 {}
693//vertse2
694#[cfg(target_arch = "wasm32")]
695unsafe impl Send for EdgesV2 {}
696#[cfg(target_arch = "wasm32")]
697unsafe impl Sync for EdgesV2 {}
698//edges
699#[cfg(target_arch = "wasm32")]
700unsafe impl Send for Edges {}
701#[cfg(target_arch = "wasm32")]
702unsafe impl Sync for Edges {}
703//faces
704#[cfg(target_arch = "wasm32")]
705unsafe impl Send for Faces {}
706#[cfg(target_arch = "wasm32")]
707unsafe impl Sync for Faces {}
708//uvs
709#[cfg(target_arch = "wasm32")]
710unsafe impl Send for UVs {}
711#[cfg(target_arch = "wasm32")]
712unsafe impl Sync for UVs {}
713//normalss
714#[cfg(target_arch = "wasm32")]
715unsafe impl Send for Normals {}
716#[cfg(target_arch = "wasm32")]
717unsafe impl Sync for Normals {}
718//tangents
719#[cfg(target_arch = "wasm32")]
720unsafe impl Send for Tangents {}
721#[cfg(target_arch = "wasm32")]
722unsafe impl Sync for Tangents {}
723//colors
724#[cfg(target_arch = "wasm32")]
725unsafe impl Send for Colors {}
726#[cfg(target_arch = "wasm32")]
727unsafe impl Sync for Colors {}
728//Diffuseimg
729#[cfg(target_arch = "wasm32")]
730unsafe impl Send for DiffuseImg {}
731#[cfg(target_arch = "wasm32")]
732unsafe impl Sync for DiffuseImg {}
733//Normalimg
734#[cfg(target_arch = "wasm32")]
735unsafe impl Send for NormalImg {}
736#[cfg(target_arch = "wasm32")]
737unsafe impl Sync for NormalImg {}
738//Metalnessimg
739#[cfg(target_arch = "wasm32")]
740unsafe impl Send for MetalnessImg {}
741#[cfg(target_arch = "wasm32")]
742unsafe impl Sync for MetalnessImg {}
743//Roughnessimg
744#[cfg(target_arch = "wasm32")]
745unsafe impl Send for RoughnessImg {}
746#[cfg(target_arch = "wasm32")]
747unsafe impl Sync for RoughnessImg {}
748//EnvironmentMap
749#[cfg(target_arch = "wasm32")]
750unsafe impl Send for EnvironmentMap {}
751#[cfg(target_arch = "wasm32")]
752unsafe impl Sync for EnvironmentMap {}