blue_engine_core/
objects.rs

1/*
2 * Blue Engine by Elham Aryanpur
3 *
4 * The license is same as the one on the root.
5*/
6
7use crate::header::{
8    glm, pixel_to_cartesian, uniform_type, Instance, InstanceRaw, Object, ObjectSettings, Pipeline,
9    PipelineData, Renderer, RotateAxis, TextureData, Textures, Vertex,
10};
11use crate::uniform_type::{Array4, Matrix};
12use crate::utils::default_resources::{DEFAULT_MATRIX_4, DEFAULT_SHADER, DEFAULT_TEXTURE};
13use crate::{ObjectStorage, RotateAmount, StringBuffer, UnsignedIntType, Vector3};
14
15impl Renderer {
16    /// Creates a new object
17    ///
18    /// Is used to define a new object and add it to the storage. This offers full customizability
19    /// and a framework for in-engine shapes to be developed.
20    ///
21    /// # Arguments
22    /// * `name` - The name of the object.
23    /// * `vertices` - A list of vertices for the object to draw with
24    /// * `indices` - A list of indices that references the vertices, defining draw order
25    /// * `settings` - The settings of the object
26    pub fn build_object(
27        &mut self,
28        name: impl StringBuffer,
29        vertices: Vec<Vertex>,
30        indices: Vec<UnsignedIntType>,
31        settings: ObjectSettings,
32    ) -> Result<Object, crate::error::Error> {
33        let vertex_buffer = self.build_vertex_buffer(&vertices, &indices);
34
35        let uniform = self.build_uniform_buffer(&vec![
36            self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4),
37            self.build_uniform_buffer_part(
38                "Color",
39                crate::uniform_type::Array4 {
40                    data: crate::utils::default_resources::DEFAULT_COLOR,
41                },
42            ),
43        ]);
44
45        let shader_source =
46            ShaderBuilder::new(DEFAULT_SHADER.to_string(), settings.camera_effect.clone());
47
48        let shader = self.build_shader(
49            name.as_str(),
50            shader_source.shader.clone(),
51            Some(&uniform.1),
52            settings.shader_settings,
53        );
54
55        let texture = self.build_texture(
56            "Default Texture",
57            TextureData::Bytes(DEFAULT_TEXTURE.to_vec()),
58            crate::header::TextureMode::Clamp,
59            //crate::header::TextureFormat::PNG
60        )?;
61
62        let instance = Instance::new([0f32, 0f32, 0f32], [0f32, 0f32, 0f32], [1f32, 1f32, 1f32]);
63
64        let instance_buffer = self.build_instance(vec![instance.to_raw()]);
65
66        Ok(Object {
67            name: name.as_arc(),
68            vertices,
69            indices,
70            pipeline: Pipeline {
71                vertex_buffer: PipelineData::Data(vertex_buffer),
72                shader: PipelineData::Data(shader),
73                texture: PipelineData::Data(texture),
74                uniform: PipelineData::Data(Some(uniform.0)),
75            },
76            instances: vec![instance],
77            instance_buffer,
78            uniform_layout: uniform.1,
79            size: Vector3::new(1f32, 1f32, 1f32),
80            position: Vector3::default(),
81            rotation: Vector3::new(0f32, 0f32, 0f32),
82            changed: false,
83            position_matrix: DEFAULT_MATRIX_4.to_im(),
84            scale_matrix: DEFAULT_MATRIX_4.to_im(),
85            rotation_matrix: DEFAULT_MATRIX_4.to_im(),
86            inverse_transformation_matrix: Matrix::from_im(nalgebra_glm::transpose(
87                &nalgebra_glm::inverse(&DEFAULT_MATRIX_4.to_im()),
88            )),
89            color: crate::uniform_type::Array4 {
90                data: crate::utils::default_resources::DEFAULT_COLOR,
91            },
92            shader_builder: shader_source,
93            shader_settings: settings.shader_settings,
94            camera_effect: settings.camera_effect,
95            uniform_buffers: vec![
96                self.build_uniform_buffer_part("Transformation Matrix", DEFAULT_MATRIX_4),
97                self.build_uniform_buffer_part(
98                    "Color",
99                    crate::uniform_type::Array4 {
100                        data: crate::utils::default_resources::DEFAULT_COLOR,
101                    },
102                ),
103            ],
104            is_visible: true,
105            render_order: 0,
106        })
107    }
108}
109
110impl ObjectStorage {
111    /// Creates a new object
112    pub fn new_object(
113        &mut self,
114        name: impl StringBuffer,
115        vertices: Vec<Vertex>,
116        indices: Vec<UnsignedIntType>,
117        settings: ObjectSettings,
118        renderer: &mut Renderer,
119    ) {
120        match renderer.build_object(name.clone(), vertices, indices, settings) {
121            Ok(object) => self.add_object(name.clone(), object),
122            Err(e) => {
123                eprintln!("Could not create a new Object: {e:#?}");
124            }
125        }
126    }
127
128    /// Adds an object to the storage
129    pub fn add_object(&mut self, key: impl StringBuffer, object: Object) {
130        fn add_object_inner(object_storage: &mut ObjectStorage, key: String, object: Object) {
131            object_storage.insert(key, object);
132        }
133        add_object_inner(self, key.as_string(), object);
134    }
135
136    /// Allows for safe update of objects
137    pub fn update_object<T: Fn(&mut Object)>(&mut self, key: impl StringBuffer, callback: T) {
138        fn update_object_inner<T: Fn(&mut Object)>(
139            object_storage: &mut ObjectStorage,
140            key: String,
141            callback: T,
142        ) {
143            let object = object_storage.get_mut(&key);
144            if let Some(object) = object {
145                callback(object);
146            }
147        }
148        update_object_inner(self, key.as_string(), callback);
149    }
150}
151
152impl Object {
153    /// Sets the name of the object
154    pub fn set_name(&mut self, name: impl StringBuffer) -> &mut Self {
155        self.name = name.as_arc();
156
157        self
158    }
159
160    /// Scales an object. e.g. 2.0 doubles the size and 0.5 halves
161    pub fn set_scale(&mut self, scale: impl Into<Vector3>) -> &mut Self {
162        let scale = scale.into();
163        self.size *= scale;
164
165        let transformation_matrix = self.scale_matrix;
166        let result = nalgebra_glm::scale(&transformation_matrix, &scale.into());
167        self.scale_matrix = result;
168        self.inverse_matrices();
169
170        self.changed = true;
171        self
172    }
173
174    /// Resizes an object in pixels which are relative to the window
175    pub fn resize(
176        &mut self,
177        width: f32,
178        height: f32,
179        depth: f32,
180        window_size: winit::dpi::PhysicalSize<u32>,
181    ) -> &mut Self {
182        let difference_in_width = if self.size.x != 0.0 && width != 0.0 {
183            let a = pixel_to_cartesian(width, window_size.width);
184            let b = pixel_to_cartesian(self.size.x, window_size.width);
185            if a != 0f32 && b != 0f32 {
186                a / b
187            } else {
188                b
189            }
190        } else {
191            0.0
192        };
193
194        let difference_in_height = if self.size.y != 0.0 && height != 0.0 {
195            let a = pixel_to_cartesian(height, window_size.height);
196            let b = pixel_to_cartesian(self.size.y, window_size.height);
197            if a != 0f32 && b != 0f32 {
198                a / b
199            } else {
200                b
201            }
202        } else {
203            0.0
204        };
205        let difference_in_depth = if self.size.z != 0.0 && depth != 0.0 {
206            let a = pixel_to_cartesian(depth, window_size.width);
207            let b = pixel_to_cartesian(self.size.z, window_size.width);
208            if a != 0f32 && b != 0f32 {
209                a / b
210            } else {
211                b
212            }
213        } else {
214            0.0
215        };
216
217        self.set_scale(Vector3::new(
218            difference_in_width,
219            difference_in_height,
220            difference_in_depth,
221        ));
222        self
223    }
224
225    /// Rotates the object in the axis you specify
226    ///
227    /// THIS METHOD IS DEPRECATED, USE [crate::Object::set_rotation] or [crate::Object::rotate]
228    #[deprecated]
229    pub fn set_rotatation(&mut self, angle: f32, axis: RotateAxis) -> &mut Self {
230        let mut rotation_matrix = self.rotation_matrix;
231        let axis = match axis {
232            RotateAxis::X => {
233                self.rotation.x += angle;
234                Vector3::x_axis()
235            }
236            RotateAxis::Y => {
237                self.rotation.y += angle;
238                Vector3::y_axis()
239            }
240            RotateAxis::Z => {
241                self.rotation.z += angle;
242                Vector3::z_axis()
243            }
244        };
245
246        rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, angle.to_radians(), &axis.into());
247        self.rotation_matrix = rotation_matrix;
248        self.inverse_matrices();
249
250        self.changed = true;
251        self
252    }
253
254    /// Sets the rotation of the object in the axis you specify
255    pub fn set_rotation(&mut self, amount: RotateAmount, axis: RotateAxis) -> &mut Self {
256        let mut rotation_matrix = self.rotation_matrix;
257
258        let amount_radians = match amount {
259            RotateAmount::Radians(amount) => amount,
260            RotateAmount::Degrees(amount) => amount.to_radians(),
261        };
262        let axis = match axis {
263            RotateAxis::X => {
264                self.rotation.x = amount_radians;
265                Vector3::x_axis()
266            }
267            RotateAxis::Y => {
268                self.rotation.y = amount_radians;
269                Vector3::y_axis()
270            }
271            RotateAxis::Z => {
272                self.rotation.z = amount_radians;
273                Vector3::z_axis()
274            }
275        };
276
277        rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis.into());
278        self.rotation_matrix = rotation_matrix;
279        self.inverse_matrices();
280
281        self.changed = true;
282        self
283    }
284
285    /// Rotates the object in the axis you specify
286    pub fn rotate(&mut self, amount: RotateAmount, axis: RotateAxis) -> &mut Self {
287        let mut rotation_matrix = self.rotation_matrix;
288
289        let amount_radians = match amount {
290            RotateAmount::Radians(amount) => amount,
291            RotateAmount::Degrees(amount) => amount.to_radians(),
292        };
293        let axis = match axis {
294            RotateAxis::X => {
295                self.rotation.x += amount_radians;
296                Vector3::x_axis()
297            }
298            RotateAxis::Y => {
299                self.rotation.y += amount_radians;
300                Vector3::y_axis()
301            }
302            RotateAxis::Z => {
303                self.rotation.z += amount_radians;
304                Vector3::z_axis()
305            }
306        };
307
308        rotation_matrix = nalgebra_glm::rotate(&rotation_matrix, amount_radians, &axis.into());
309        self.rotation_matrix = rotation_matrix;
310        self.inverse_matrices();
311
312        self.changed = true;
313        self
314    }
315
316    /// Moves the object by the amount you specify in the axis you specify
317    pub fn set_translation(&mut self, new_pos: impl Into<Vector3>) -> &mut Self {
318        self.position -= new_pos.into();
319
320        let mut position_matrix = self.position_matrix;
321        position_matrix = nalgebra_glm::translate(&position_matrix, &self.position.into());
322        self.position_matrix = position_matrix;
323
324        self.inverse_matrices();
325        self.changed = true;
326        self
327    }
328
329    /// Sets the position of the object in 3D space relative to the window
330    pub fn set_position(&mut self, new_pos: impl Into<Vector3>) -> &mut Self {
331        let new_pos = new_pos.into();
332        self.set_translation((self.position - new_pos) * -1f32);
333
334        self.position.x = new_pos.x;
335        self.position.y = new_pos.y;
336        self.position.z = new_pos.z;
337        self
338    }
339
340    /// Changes the color of the object. If textures exist, the color of textures will change
341    pub fn set_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) -> &mut Self {
342        self.color = Array4 {
343            data: [red, green, blue, alpha],
344        };
345        self.changed = true;
346        self
347    }
348
349    /// Changes the render order of the Object.
350    ///
351    /// Objects with higher number get rendered later and appear "on top" when occupying the same space
352    pub fn set_render_order(&mut self, render_order: usize) -> &mut Self {
353        self.render_order = render_order;
354
355        self
356    }
357
358    /// Replaces the object's texture with provided one
359    pub fn set_texture(&mut self, texture: Textures) -> &mut Self {
360        self.pipeline.texture = PipelineData::Data(texture);
361        self.changed = true;
362
363        self
364    }
365
366    /// This will flag object as changed and altered, leading to rebuilding parts, or entirety on next frame.
367    /// Best used if you directly altered fields of the object. The functions normally flag the object as
368    /// changed on every call anyways. But this function is to manually flag it yourself.
369    pub fn flag_as_changed(&mut self, is_changed: bool) {
370        self.changed = is_changed;
371    }
372
373    /// Sets if the object will be rendered or not
374    pub fn set_visibility(&mut self, is_visible: bool) {
375        self.is_visible = is_visible;
376    }
377
378    /// build an inverse of the transformation matrix to be sent to the gpu for lighting and other things.
379    pub fn inverse_matrices(&mut self) {
380        self.inverse_transformation_matrix =
381            Matrix::from_im(nalgebra_glm::transpose(&nalgebra_glm::inverse(
382                &(self.position_matrix * self.rotation_matrix * self.scale_matrix),
383            )));
384    }
385
386    /// Update and apply changes done to an object
387    pub fn update(&mut self, renderer: &mut Renderer) {
388        self.update_vertex_buffer(renderer);
389        self.update_uniform_buffer(renderer);
390        self.update_shader(renderer);
391        self.update_instance_buffer(renderer);
392        self.changed = false;
393    }
394
395    /// Update and apply changes done to an object and returns a pipeline
396    pub fn update_and_return(
397        &mut self,
398        renderer: &mut Renderer,
399    ) -> (crate::VertexBuffers, crate::UniformBuffers, crate::Shaders) {
400        let vertex_buffer = self.update_vertex_buffer_and_return(renderer);
401        let uniform_buffer = self.update_uniform_buffer_and_return(renderer);
402        let shader = self.update_shader_and_return(renderer);
403        self.changed = false;
404        (vertex_buffer, uniform_buffer, shader)
405    }
406
407    /// Update and apply changes done to the vertex buffer
408    pub fn update_vertex_buffer(&mut self, renderer: &mut Renderer) {
409        let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices);
410        self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer);
411    }
412
413    /// Returns the buffer with ownership
414    pub fn update_vertex_buffer_and_return(
415        &mut self,
416        renderer: &mut Renderer,
417    ) -> crate::VertexBuffers {
418        let updated_buffer = renderer.build_vertex_buffer(&self.vertices, &self.indices);
419        let updated_buffer_2 = renderer.build_vertex_buffer(&self.vertices, &self.indices);
420        self.pipeline.vertex_buffer = PipelineData::Data(updated_buffer);
421
422        updated_buffer_2
423    }
424
425    /// Update and apply changes done to the shader
426    pub fn update_shader(&mut self, renderer: &mut Renderer) {
427        let updated_shader = renderer.build_shader(
428            self.name.as_ref(),
429            self.shader_builder.shader.clone(),
430            Some(&self.uniform_layout),
431            self.shader_settings,
432        );
433        self.pipeline.shader = PipelineData::Data(updated_shader);
434    }
435
436    /// Returns the buffer with ownership
437    pub fn update_shader_and_return(&mut self, renderer: &mut Renderer) -> crate::Shaders {
438        let updated_shader = renderer.build_shader(
439            self.name.as_ref(),
440            self.shader_builder.shader.clone(),
441            Some(&self.uniform_layout),
442            self.shader_settings,
443        );
444        let updated_shader2 = renderer.build_shader(
445            self.name.as_ref(),
446            self.shader_builder.shader.clone(),
447            Some(&self.uniform_layout),
448            self.shader_settings,
449        );
450        self.pipeline.shader = PipelineData::Data(updated_shader);
451
452        updated_shader2
453    }
454
455    /// Update and apply changes done to the uniform buffer
456    pub fn update_uniform_buffer(&mut self, renderer: &mut Renderer) {
457        self.uniform_buffers[0] = renderer.build_uniform_buffer_part(
458            "Transformation Matrix",
459            uniform_type::Matrix::from_im(
460                self.position_matrix * self.rotation_matrix * self.scale_matrix,
461            ),
462        );
463        self.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color);
464
465        let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers);
466
467        self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0));
468        self.uniform_layout = updated_buffer.1;
469    }
470
471    /// Returns the buffer with ownership
472    pub fn update_uniform_buffer_and_return(
473        &mut self,
474        renderer: &mut Renderer,
475    ) -> crate::UniformBuffers {
476        self.uniform_buffers[0] = renderer.build_uniform_buffer_part(
477            "Transformation Matrix",
478            uniform_type::Matrix::from_im(
479                self.position_matrix * self.rotation_matrix * self.scale_matrix,
480            ),
481        );
482        self.uniform_buffers[1] = renderer.build_uniform_buffer_part("Color", self.color);
483
484        let updated_buffer = renderer.build_uniform_buffer(&self.uniform_buffers);
485        let updated_buffer2 = renderer.build_uniform_buffer(&self.uniform_buffers);
486
487        self.pipeline.uniform = PipelineData::Data(Some(updated_buffer.0));
488        self.uniform_layout = updated_buffer.1;
489
490        updated_buffer2.0
491    }
492
493    /// Updates the instance buffer
494    pub fn update_instance_buffer(&mut self, renderer: &mut Renderer) {
495        let instance_data = self
496            .instances
497            .iter()
498            .map(Instance::to_raw)
499            .collect::<Vec<_>>();
500        let instance_buffer = renderer.build_instance(instance_data);
501        self.instance_buffer = instance_buffer;
502    }
503
504    /// Returns the buffer with ownership
505    pub fn update_instance_buffer_and_return(&mut self, renderer: &mut Renderer) -> wgpu::Buffer {
506        let instance_data = self
507            .instances
508            .iter()
509            .map(Instance::to_raw)
510            .collect::<Vec<_>>();
511        let instance_buffer = renderer.build_instance(instance_data.clone());
512        let instance_buffer2 = renderer.build_instance(instance_data);
513
514        self.instance_buffer = instance_buffer;
515        instance_buffer2
516    }
517
518    // ============================= FOR COPY OF PIPELINES =============================
519    /// References another object's vertices
520    pub fn reference_vertices(&mut self, object_id: impl StringBuffer) -> &mut Self {
521        self.pipeline.vertex_buffer = PipelineData::Copy(object_id.as_string());
522        self
523    }
524
525    /// References another object's shader
526    pub fn reference_shader(&mut self, object_id: impl StringBuffer) -> &mut Self {
527        self.pipeline.shader = PipelineData::Copy(object_id.as_string());
528        self
529    }
530
531    /// References another object's texture
532    pub fn reference_texture(&mut self, object_id: impl StringBuffer) -> &mut Self {
533        self.pipeline.texture = PipelineData::Copy(object_id.as_string());
534        self
535    }
536
537    /// References another object's uniform buffer
538    pub fn reference_uniform_buffer(&mut self, object_id: impl StringBuffer) -> &mut Self {
539        self.pipeline.uniform = PipelineData::Copy(object_id.as_string());
540        self
541    }
542
543    // ============================= Instances =============================
544    /// Add an instance to the object
545    pub fn add_instance(&mut self, instance: Instance) -> &mut Self {
546        self.instances.push(instance);
547        self.changed = true;
548        self
549    }
550}
551
552/// Configuration type for ShaderBuilder
553pub type ShaderConfigs = Vec<(String, Box<dyn Fn(Option<std::sync::Arc<str>>) -> String>)>;
554
555/// Helps with building and updating shader code
556pub struct ShaderBuilder {
557    /// the shader itself
558    pub shader: String,
559    /// Should the camera effect be applied
560    pub camera_effect: Option<std::sync::Arc<str>>,
561    /// configurations to be applied to the shader
562    pub configs: ShaderConfigs,
563}
564
565impl ShaderBuilder {
566    /// Creates a new shader builder
567    pub fn new(shader_source: String, camera_effect: Option<std::sync::Arc<str>>) -> Self {
568        let mut shader_builder = Self {
569            shader: shader_source,
570            camera_effect,
571            configs: vec![
572                (
573                    "//@CAMERA_STRUCT".to_string(),
574                    Box::new(|camera_effect| {
575                        if camera_effect.is_some() {
576                            r#"struct CameraUniforms {
577                            camera_matrix: mat4x4<f32>,
578                        };
579                        @group(1) @binding(0)
580                        var<uniform> camera_uniform: CameraUniforms;"#
581                                .to_string()
582                        } else {
583                            "".to_string()
584                        }
585                    }),
586                ),
587                (
588                    "//@CAMERA_VERTEX".to_string(),
589                    Box::new(|camera_effect| {
590                        if camera_effect.is_some() {
591                            r#"out.position = camera_uniform.camera_matrix * model_matrix * (transform_uniform.transform_matrix * vec4<f32>(input.position, 1.0));"#
592                        .to_string()
593                        } else {
594                            r#"out.position = model_matrix * (transform_uniform.transform_matrix * vec4<f32>(input.position, 1.0));"#.to_string()
595                        }
596                    }),
597                ),
598            ],
599        };
600        shader_builder.build();
601
602        shader_builder
603    }
604
605    /// Sets the new shader
606    pub fn set_shader(&mut self, new_shader: String) {
607        self.shader = new_shader;
608        self.build();
609    }
610
611    /// Builds the shader with the configuration defined
612    pub fn build(&mut self) {
613        for i in &self.configs {
614            self.shader = self.shader.replace(&i.0, &i.1(self.camera_effect.clone()));
615        }
616    }
617}
618
619impl Instance {
620    /// Creates a new instance
621    pub fn new(
622        position: impl Into<Vector3>,
623        rotation: impl Into<Vector3>,
624        scale: impl Into<Vector3>,
625    ) -> Self {
626        Self {
627            position: position.into(),
628            rotation: rotation.into(),
629            scale: scale.into(),
630        }
631    }
632
633    /// Gathers all information and builds a Raw Instance to be sent to GPU
634    pub fn to_raw(&self) -> InstanceRaw {
635        let position_matrix = glm::translate(&DEFAULT_MATRIX_4.to_im(), &self.position.into());
636        let rotation_matrix =
637            nalgebra_glm::rotate(&DEFAULT_MATRIX_4.to_im(), 0f32, &self.rotation.into());
638        let scale_matrix = glm::scale(&DEFAULT_MATRIX_4.to_im(), &self.scale.into());
639        InstanceRaw {
640            model: Matrix::from_im(position_matrix * rotation_matrix * scale_matrix),
641        }
642    }
643
644    /// Sets the position
645    pub fn set_position(&mut self, position: impl Into<Vector3>) {
646        self.position = position.into();
647    }
648
649    /// Sets the rotation
650    pub fn set_rotation(&mut self, rotation: impl Into<Vector3>) {
651        self.rotation = rotation.into();
652    }
653
654    /// Sets the scale
655    pub fn set_scale(&mut self, scale: impl Into<Vector3>) {
656        self.scale = scale.into();
657    }
658}
659
660impl Default for Instance {
661    fn default() -> Self {
662        Self {
663            position: Vector3::default(),
664            rotation: Vector3::default(),
665            scale: Vector3::new(1.0, 1.0, 1.0),
666        }
667    }
668}
669
670impl InstanceRaw {
671    /// Instance's layout description
672    pub fn desc() -> wgpu::VertexBufferLayout<'static> {
673        use std::mem;
674        wgpu::VertexBufferLayout {
675            array_stride: mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
676            // We need to switch from using a step mode of Vertex to Instance
677            // This means that our shaders will only change to use the next
678            // instance when the shader starts processing a new instance
679            step_mode: wgpu::VertexStepMode::Instance,
680            attributes: &[
681                // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot
682                // for each vec4. We'll have to reassemble the mat4 in the shader.
683                wgpu::VertexAttribute {
684                    offset: 0,
685                    shader_location: 3,
686                    format: wgpu::VertexFormat::Float32x4,
687                },
688                wgpu::VertexAttribute {
689                    offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
690                    shader_location: 4,
691                    format: wgpu::VertexFormat::Float32x4,
692                },
693                wgpu::VertexAttribute {
694                    offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
695                    shader_location: 5,
696                    format: wgpu::VertexFormat::Float32x4,
697                },
698                wgpu::VertexAttribute {
699                    offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
700                    shader_location: 6,
701                    format: wgpu::VertexFormat::Float32x4,
702                },
703            ],
704        }
705    }
706}