tiny_game_framework/graphics/
particle.rs

1use std::{ptr, ffi::CString};
2
3use gl::{*, types::*};
4use glam::{vec2, vec3, Mat4, Quat, Vec3, Vec4};
5use once_cell::sync::Lazy;
6
7use crate::{bind_buffer, cstr, gen_attrib_pointers, rand_betw, rand_vec3, EventLoop, InstanceData, Renderer, Shader, Vertex, PARTICLE_SHADER_FS, PARTICLE_SHADER_VS};
8
9pub static PARTICLE_SHADER: Lazy<Shader> = Lazy::new(|| {
10    Shader::new_pipeline(PARTICLE_SHADER_VS, PARTICLE_SHADER_FS)
11});
12
13#[derive(PartialEq, Debug, Copy, Clone)]
14pub struct ParticleInstanceData {
15    pub position: Vec3,
16    pub lifespan: f32,
17    pub velocity: Vec3,
18}
19
20
21#[derive(Default)]
22pub struct Particle {
23    /* particle stuff */
24    pub position: Vec3,
25    pub velocity: Vec3,
26
27    pub has_gravity: bool,
28    pub size: f32,
29
30    pub count: i32,
31
32    pub spread: f32,
33
34    /* rendering stuff */
35
36    // image_path: String,
37    pub mesh: ParticleMesh,
38}
39
40pub struct ParticleMesh {
41    vertices: Vec<Vertex>,
42    indices: Vec<u32>,
43
44    pub VAO: u32,
45    EBO: u32,
46    VBO: u32,
47    
48    pub instance_data: Vec<ParticleInstanceData>,
49
50    pub instance_buffer: u32,
51
52    shader: Shader,
53}
54
55impl Default for ParticleMesh {
56    fn default() -> Self {
57        Self::new(5, Vec3::ZERO)
58    }
59}
60
61impl ParticleMesh {
62    pub fn new(count: i32, initial_position: Vec3) -> Self {
63        let vertices = vec![
64            Vertex::new(vec3(0.0, 0.0, 0.0), Vec4::ONE, vec2(0.0, 0.0), vec3(0., 0., 1.)), // Bottom-left
65            Vertex::new(vec3(0.0, 1.0, 0.0), Vec4::ONE, vec2(0.0, 1.0), vec3(0., 0., 1.)), // Top-left
66            Vertex::new(vec3(1.0, 0.0, 0.0), Vec4::ONE, vec2(1.0, 0.0), vec3(0., 0., 1.)), // Bottom-right
67            Vertex::new(vec3(1.0, 1.0, 0.0), Vec4::ONE, vec2(1.0, 1.0), vec3(0., 0., 1.)), // Top-right
68        ];
69
70        let indices = vec![0, 2, 1, 2, 3, 1];
71
72        let mut instance_data = vec![];
73        for _ in 0..count {
74            instance_data.push(ParticleInstanceData {
75                position: initial_position,
76                lifespan: 1.0, 
77                velocity: Vec3::ZERO,
78            });
79        }
80
81        let mut mesh = Self {
82            vertices,
83            indices,
84            VAO: 0,
85            VBO: 0,
86            EBO: 0,
87            instance_buffer: 0,
88            instance_data,
89            shader: *PARTICLE_SHADER,
90        };
91
92        mesh
93    }
94
95    pub fn update_instance_data(&mut self, data: Vec<ParticleInstanceData>) {
96        self.instance_data = data;
97        unsafe {
98            BindBuffer(ARRAY_BUFFER, self.instance_buffer);
99            BufferSubData(
100                ARRAY_BUFFER, 
101                0, 
102                (self.instance_data.len() * std::mem::size_of::<ParticleInstanceData>()) as isize, 
103                self.instance_data.as_ptr() as *const _
104            );
105        }
106    }
107    
108    pub unsafe fn setup_mesh(&mut self) {
109        GenVertexArrays(1, &mut self.VAO);
110        GenBuffers(1, &mut self.VBO);
111        GenBuffers(1, &mut self.EBO);
112        
113        BindVertexArray(self.VAO);
114        
115        bind_buffer!(ARRAY_BUFFER, self.VBO, self.vertices);
116        bind_buffer!(ELEMENT_ARRAY_BUFFER, self.EBO, self.indices);
117        gen_attrib_pointers!(Vertex, 0 => position: 3, 1 => color: 4);
118
119        GenBuffers(1, &mut self.instance_buffer);
120        bind_buffer!(ARRAY_BUFFER, self.instance_buffer, self.instance_data);
121        
122        gen_attrib_pointers!(ParticleInstanceData, 2 => position: 3);
123        VertexAttribDivisor(2, 1);
124        
125        BindVertexArray(0);
126    }
127
128    pub fn destroy(&mut self) {
129        unsafe {
130            DeleteVertexArrays(1, &self.VAO);
131            DeleteBuffers(1, &self.EBO);
132            DeleteBuffers(1, &self.VBO);
133        }
134    }
135
136    pub unsafe fn draw(&self, model_matrix: Mat4) {
137        BindVertexArray(self.VAO);
138        self.shader.use_shader();
139
140        self.shader.uniform_mat4fv(cstr!("model"), &model_matrix.to_cols_array());
141
142        DrawElementsInstanced(TRIANGLES, self.indices.len() as i32, UNSIGNED_INT, ptr::null(), self.instance_data.len() as i32);
143        BindVertexArray(0);
144        UseProgram(0);
145    }
146}
147
148
149impl Particle {
150    pub fn new(
151        position: Vec3, 
152        velocity: Vec3, 
153        count: i32,
154        size: f32,
155        has_gravity: bool,
156        spread: f32,
157    ) -> Self {
158        let mut mesh = ParticleMesh::new(count, position);
159
160        let mut subscribed_instance_data = vec![];
161        for _ in 0..count {
162            subscribed_instance_data.push(ParticleInstanceData {
163                position: Vec3::ZERO,
164                velocity: velocity * rand_vec3() * spread,
165                lifespan: rand_betw(0.0, 5.0), // initialize with some random lifespam
166            });
167        }
168
169        mesh.update_instance_data(subscribed_instance_data);
170        unsafe { mesh.setup_mesh(); };
171
172        Self {
173            position,
174            velocity,
175            count,
176            size,
177            has_gravity,
178            spread,
179            mesh,
180        }
181    }
182
183    pub fn update(&mut self, el: &EventLoop) {
184        let instance_data = &self.mesh.instance_data;
185        let mut subscribed_instance_data = vec![];
186
187        for i in 0..self.count {
188            let mut particle = instance_data[i as usize];
189            particle.position += particle.velocity * el.dt * 1.0 / self.size;
190            particle.lifespan -= el.dt;
191
192            if particle.lifespan <= 0.0 {
193                particle.position = Vec3::ZERO;
194                particle.velocity = self.velocity * rand_vec3() * self.spread;
195                particle.lifespan = rand_betw(0.5, 5.0);
196            }
197            if self.has_gravity {
198                particle.velocity.y -= 1.0 * el.dt;                
199            }
200            subscribed_instance_data.push(particle);
201        }
202
203        self.mesh.update_instance_data(subscribed_instance_data);
204        // gg
205    }
206
207
208    pub fn draw(&self) {
209        let model_matrix = 
210            Mat4::from_translation(self.position) *
211            Mat4::from_quat(Quat::default()) *
212            Mat4::from_scale(vec3(self.size, self.size, self.size));
213        
214            
215        unsafe {
216            self.mesh.draw(model_matrix);
217        }
218    }
219}
220
221impl Renderer {
222    pub fn add_particle(&mut self, name: &str, particle: Particle) {
223        self.particles.insert(name.to_owned(), particle);// .unwrap();
224    }
225
226    pub fn remove_particle(&mut self, name: &str) {
227        let particle = self.particles.get_mut(name).unwrap();
228        particle.mesh.destroy(); // gotta make sure to clear the opengl's resources first
229        self.particles.remove(name).unwrap();
230    }
231}