1use super::emission::{
4 EmissionParams, EmissionType, ParticleRng, create_planar, create_spherical, create_spline,
5};
6use super::particle::Particle;
7use super::{PARTICLE_COORDINATE_FIX, TEXELS_PER_PARTICLE};
8use crate::chunks::particle_emitter::{M2ParticleEmitter, M2ParticleEmitterType, M2ParticleFlags};
9
10#[derive(Debug, Clone, Default)]
12pub struct EmitterParams {
13 pub enabled: bool,
15 pub gravity: [f32; 3],
17 pub emission_speed: f32,
19 pub speed_variation: f32,
21 pub vertical_range: f32,
23 pub horizontal_range: f32,
25 pub lifespan: f32,
27 pub emission_rate: f32,
29 pub emission_area_length: f32,
31 pub emission_area_width: f32,
33 pub z_source: f32,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum BlendMode {
40 #[default]
42 Opaque,
43 AlphaBlend,
45 Additive,
47 Modulate,
49 AlphaKey,
51}
52
53impl BlendMode {
54 pub fn from_u8(value: u8) -> Self {
56 match value {
57 0 => BlendMode::Opaque,
58 1 => BlendMode::AlphaKey,
59 2 => BlendMode::AlphaBlend,
60 3 => BlendMode::Additive,
61 4 => BlendMode::Modulate,
62 _ => BlendMode::AlphaBlend,
63 }
64 }
65
66 pub fn alpha_test(&self) -> f32 {
68 match self {
69 BlendMode::Opaque => -1.0,
70 BlendMode::AlphaKey => 0.501_960_8,
71 _ => 0.003_921_569,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct ParticleEmitter {
79 emission_type: EmissionType,
81 particles: Vec<Particle>,
83 rng: ParticleRng,
85 model_matrix: [f32; 16],
87 coordinate_fix: [f32; 16],
89 wind: [f32; 3],
91 position: [f32; 3],
93 drag: f32,
95 particles_to_emit: f32,
97 lifespan_variance: f32,
99 max_particles: usize,
101 bone_index: u16,
103 texture_index: u16,
105 blend_mode: BlendMode,
107 flags: M2ParticleFlags,
109 pub params: EmitterParams,
111 pub tex_scale_x: f32,
113 pub tex_scale_y: f32,
115 tex_col_bits: u32,
117 tex_col_mask: u32,
119}
120
121impl ParticleEmitter {
122 pub fn new(m2_emitter: &M2ParticleEmitter) -> Self {
124 let emission_type = match m2_emitter.emitter_type {
125 M2ParticleEmitterType::Plane => EmissionType::Planar,
126 M2ParticleEmitterType::Sphere => EmissionType::Spherical,
127 M2ParticleEmitterType::Spline => EmissionType::Spline,
128 _ => EmissionType::Point,
129 };
130
131 let max_lifespan = m2_emitter.lifetime.max(m2_emitter.max_lifetime);
133 let max_rate = m2_emitter.emission_rate.max(m2_emitter.max_emission_rate);
134 let max_particles = ((max_lifespan * max_rate * 1.5) as usize).max(16);
135
136 let tex_cols = 1u32; let tex_rows = 1u32; let tex_scale_x = 1.0 / tex_rows as f32;
141 let tex_scale_y = 1.0 / tex_cols as f32;
142 let tex_col_bits = (tex_cols as f32).log2().ceil() as u32;
143 let tex_col_mask = (1 << tex_col_bits) - 1;
144
145 Self {
146 emission_type,
147 particles: Vec::with_capacity(max_particles),
148 rng: ParticleRng::new(42), model_matrix: identity_matrix(),
150 coordinate_fix: PARTICLE_COORDINATE_FIX,
151 wind: [0.0, 0.0, 0.0],
152 position: [
153 m2_emitter.position.x,
154 m2_emitter.position.y,
155 m2_emitter.position.z,
156 ],
157 drag: 0.0, particles_to_emit: 0.0,
159 lifespan_variance: m2_emitter.max_lifetime - m2_emitter.min_lifetime,
160 max_particles,
161 bone_index: m2_emitter.bone_index,
162 texture_index: m2_emitter.texture_index,
163 blend_mode: BlendMode::from_u8(m2_emitter.blending_type),
164 flags: m2_emitter.flags,
165 params: EmitterParams {
166 enabled: true,
167 gravity: [0.0, 0.0, -m2_emitter.gravity],
168 emission_speed: m2_emitter.emission_velocity,
169 speed_variation: m2_emitter.speed_variation,
170 vertical_range: m2_emitter.vertical_range,
171 horizontal_range: m2_emitter.horizontal_range,
172 lifespan: m2_emitter.lifetime,
173 emission_rate: m2_emitter.emission_rate,
174 emission_area_length: m2_emitter.emission_area_length,
175 emission_area_width: m2_emitter.emission_area_width,
176 z_source: 0.0,
177 },
178 tex_scale_x,
179 tex_scale_y,
180 tex_col_bits,
181 tex_col_mask,
182 }
183 }
184
185 pub fn bone_index(&self) -> u16 {
187 self.bone_index
188 }
189
190 pub fn texture_index(&self) -> u16 {
192 self.texture_index
193 }
194
195 pub fn blend_mode(&self) -> BlendMode {
197 self.blend_mode
198 }
199
200 pub fn particle_count(&self) -> usize {
202 self.particles.len()
203 }
204
205 pub fn max_particles(&self) -> usize {
207 self.max_particles
208 }
209
210 fn translate_with_bone(&self) -> bool {
212 !self.flags.contains(M2ParticleFlags::FOLLOW_EMITTER)
213 }
214
215 fn particles_go_up(&self) -> bool {
217 self.flags.contains(M2ParticleFlags::SPHERE_AS_SOURCE)
218 }
219
220 pub fn update(
227 &mut self,
228 dt_ms: f32,
229 bone_transform: &[f32; 16],
230 bone_post_billboard: &[f32; 16],
231 ) {
232 let dt = dt_ms / 1000.0;
233
234 self.update_model_matrix(bone_transform, bone_post_billboard);
236
237 if self.params.enabled {
239 let emission_rate =
240 self.params.emission_rate + self.rng.random_range(self.params.emission_rate * 0.1);
241 self.particles_to_emit += emission_rate * dt;
242
243 while self.particles_to_emit > 1.0 && self.particles.len() < self.max_particles {
244 self.create_particle();
245 self.particles_to_emit -= 1.0;
246 }
247 }
248
249 let force = [
251 self.wind[0] - self.params.gravity[0],
252 self.wind[1] - self.params.gravity[1],
253 self.wind[2] - self.params.gravity[2],
254 ];
255
256 self.particles.retain_mut(|particle| {
258 particle.age += dt;
259 if !particle.is_alive() {
260 return false;
261 }
262
263 particle.update_physics(dt, force, self.drag);
265
266 true
267 });
268 }
269
270 pub fn update_params(&mut self, params: EmitterParams) {
274 self.params = params;
275 }
276
277 fn update_model_matrix(&mut self, bone_transform: &[f32; 16], bone_post_billboard: &[f32; 16]) {
279 let mut local = identity_matrix();
281 local[12] = self.position[0];
282 local[13] = self.position[1];
283 local[14] = self.position[2];
284
285 let temp = mat4_multiply(bone_transform, &local);
287 let combined = mat4_multiply(bone_post_billboard, &temp);
288
289 self.model_matrix = mat4_multiply(&combined, &self.coordinate_fix);
291 }
292
293 fn create_particle(&mut self) {
295 let emission_params = EmissionParams {
296 area_length: self.params.emission_area_length,
297 area_width: self.params.emission_area_width,
298 speed: self.params.emission_speed,
299 speed_variation: self.params.speed_variation,
300 vertical_range: self.params.vertical_range,
301 horizontal_range: self.params.horizontal_range,
302 z_source: self.params.z_source,
303 lifespan: self.params.lifespan,
304 lifespan_variance: self.lifespan_variance,
305 };
306
307 let particles_go_up = self.particles_go_up();
309
310 let mut particle = match self.emission_type {
311 EmissionType::Planar => create_planar(&emission_params, &mut self.rng),
312 EmissionType::Spherical => {
313 create_spherical(&emission_params, &mut self.rng, particles_go_up)
314 }
315 EmissionType::Spline => {
316 create_spline(&emission_params, &mut self.rng, [0.0, 0.0, 0.0])
318 }
319 EmissionType::Point => {
320 let speed = self.params.emission_speed
322 * (1.0 + self.rng.random_range(self.params.speed_variation));
323 Particle::new(
324 [0.0, 0.0, 0.0],
325 [0.0, 0.0, speed],
326 self.params.lifespan + self.rng.random_range(self.lifespan_variance),
327 )
328 }
329 };
330
331 if !self.translate_with_bone() {
333 particle.position = transform_point(&particle.position, &self.model_matrix);
334 particle.velocity = transform_vector(&particle.velocity, &self.model_matrix);
335 }
336
337 self.particles.push(particle);
338 }
339
340 pub fn fill_texture_data(&self) -> Vec<f32> {
348 let mut data = vec![0.0; self.max_particles * TEXELS_PER_PARTICLE * 4];
349
350 for (i, particle) in self.particles.iter().enumerate() {
351 let base = i * TEXELS_PER_PARTICLE * 4;
352
353 let pos = if self.translate_with_bone() {
355 transform_point(&particle.position, &self.model_matrix)
356 } else {
357 particle.position
358 };
359
360 data[base] = pos[0];
362 data[base + 1] = pos[1];
363 data[base + 2] = pos[2];
364 data[base + 3] = 0.0;
365
366 data[base + 4] = particle.color[0];
368 data[base + 5] = particle.color[1];
369 data[base + 6] = particle.color[2];
370 data[base + 7] = particle.color[3];
371
372 data[base + 8] = particle.scale[0];
374 data[base + 9] = particle.scale[1];
375 data[base + 10] = 0.0;
376 data[base + 11] = 0.0;
377
378 data[base + 12] = particle.tex_coord_head[0];
380 data[base + 13] = particle.tex_coord_head[1];
381 data[base + 14] = 0.0;
382 data[base + 15] = 0.0;
383 }
384
385 data
386 }
387
388 #[allow(dead_code)]
390 fn extract_tex_coords(&self, cell: u16) -> [f32; 2] {
391 let x_int = cell as u32 & self.tex_col_mask;
392 let y_int = cell as u32 >> self.tex_col_bits;
393 [
394 x_int as f32 * self.tex_scale_x,
395 y_int as f32 * self.tex_scale_y,
396 ]
397 }
398}
399
400fn identity_matrix() -> [f32; 16] {
404 [
405 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
406 ]
407}
408
409fn mat4_multiply(a: &[f32; 16], b: &[f32; 16]) -> [f32; 16] {
411 let mut result = [0.0f32; 16];
412
413 for col in 0..4 {
414 for row in 0..4 {
415 let mut sum = 0.0;
416 for k in 0..4 {
417 sum += a[k * 4 + row] * b[col * 4 + k];
418 }
419 result[col * 4 + row] = sum;
420 }
421 }
422
423 result
424}
425
426fn transform_point(p: &[f32; 3], m: &[f32; 16]) -> [f32; 3] {
428 let x = m[0] * p[0] + m[4] * p[1] + m[8] * p[2] + m[12];
429 let y = m[1] * p[0] + m[5] * p[1] + m[9] * p[2] + m[13];
430 let z = m[2] * p[0] + m[6] * p[1] + m[10] * p[2] + m[14];
431 [x, y, z]
432}
433
434fn transform_vector(v: &[f32; 3], m: &[f32; 16]) -> [f32; 3] {
436 let x = m[0] * v[0] + m[4] * v[1] + m[8] * v[2];
437 let y = m[1] * v[0] + m[5] * v[1] + m[9] * v[2];
438 let z = m[2] * v[0] + m[6] * v[1] + m[10] * v[2];
439 [x, y, z]
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn test_blend_mode_from_u8() {
448 assert_eq!(BlendMode::from_u8(0), BlendMode::Opaque);
449 assert_eq!(BlendMode::from_u8(1), BlendMode::AlphaKey);
450 assert_eq!(BlendMode::from_u8(2), BlendMode::AlphaBlend);
451 assert_eq!(BlendMode::from_u8(3), BlendMode::Additive);
452 assert_eq!(BlendMode::from_u8(4), BlendMode::Modulate);
453 }
454
455 #[test]
456 fn test_identity_matrix() {
457 let m = identity_matrix();
458 assert_eq!(m[0], 1.0);
459 assert_eq!(m[5], 1.0);
460 assert_eq!(m[10], 1.0);
461 assert_eq!(m[15], 1.0);
462 }
463
464 #[test]
465 fn test_transform_point() {
466 let m = identity_matrix();
467 let p = [1.0, 2.0, 3.0];
468 let result = transform_point(&p, &m);
469 assert_eq!(result, p);
470 }
471
472 #[test]
473 fn test_transform_point_translation() {
474 let mut m = identity_matrix();
475 m[12] = 10.0; m[13] = 20.0; m[14] = 30.0; let p = [1.0, 2.0, 3.0];
480 let result = transform_point(&p, &m);
481 assert_eq!(result, [11.0, 22.0, 33.0]);
482 }
483
484 #[test]
485 fn test_mat4_multiply_identity() {
486 let a = identity_matrix();
487 let b = identity_matrix();
488 let result = mat4_multiply(&a, &b);
489 assert_eq!(result, identity_matrix());
490 }
491}