1#[cfg(feature = "serde")]
2mod serde_serializable;
3
4use macroquad::prelude::*;
5use macroquad::window::miniquad::*;
6
7#[cfg(feature = "nanoserde")]
8use nanoserde::{DeJson, SerJson};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "serde")]
14use crate::serde_serializable::{post_processing_opt, vec2_def, ColorDef};
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub enum Interpolation {
20 Linear,
21 Bezier,
22}
23
24#[derive(Debug, Clone)]
25#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct Curve {
28 pub points: Vec<(f32, f32)>,
30 pub interpolation: Interpolation,
33 pub resolution: usize,
35}
36
37impl Curve {
38 fn batch(&self) -> BatchedCurve {
39 if self.interpolation == Interpolation::Bezier {
40 unimplemented!()
41 }
42
43 let step_f32 = 1.0 / self.resolution as f32;
44 let mut x = 0.0;
45 let mut points = Vec::with_capacity(self.resolution);
46
47 for curve_part in self.points.windows(2) {
48 let start = curve_part[0];
49 let end = curve_part[1];
50
51 while x <= end.0 {
52 let t = (x - start.0) / (end.0 - start.0);
53 let point = start.1 + (end.1 - start.1) * t;
54 points.push(point);
55 x += step_f32;
56 }
57 }
58
59 BatchedCurve { points }
60 }
61}
62
63#[derive(Debug, Clone)]
64pub struct BatchedCurve {
65 pub points: Vec<f32>,
66}
67
68impl BatchedCurve {
69 fn get(&self, t: f32) -> f32 {
70 let t_scaled = t * self.points.len() as f32;
71 let previous_ix = (t_scaled as usize).min(self.points.len() - 1);
72 let next_ix = (previous_ix + 1).min(self.points.len() - 1);
73 let previous = self.points[previous_ix];
74 let next = self.points[next_ix];
75
76 previous + (next - previous) * (t_scaled - previous_ix as f32)
77 }
78}
79impl Default for Curve {
80 fn default() -> Curve {
81 Curve {
82 points: vec![],
83 interpolation: Interpolation::Linear,
84 resolution: 20,
85 }
86 }
87}
88
89#[derive(Copy, Clone, PartialEq, Debug)]
90#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
91#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92pub enum EmissionShape {
93 Point,
94 Rect { width: f32, height: f32 },
95 Sphere { radius: f32 },
96}
97
98#[derive(Copy, Clone, PartialEq, Debug)]
99#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
100#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
101pub struct ColorSerializable {
102 pub r: f32,
103 pub g: f32,
104 pub b: f32,
105 pub a: f32,
106}
107
108impl From<&Color> for ColorSerializable {
109 fn from(color: &Color) -> ColorSerializable {
110 ColorSerializable {
111 r: color.r,
112 g: color.g,
113 b: color.b,
114 a: color.a,
115 }
116 }
117}
118
119impl From<&ColorSerializable> for Color {
120 fn from(color: &ColorSerializable) -> Color {
121 Color {
122 r: color.r,
123 g: color.g,
124 b: color.b,
125 a: color.a,
126 }
127 }
128}
129
130#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct Vec2Serializable {
133 x: f32,
134 y: f32,
135}
136
137impl From<&Vec2> for Vec2Serializable {
138 fn from(vec: &Vec2) -> Vec2Serializable {
139 Vec2Serializable { x: vec.x, y: vec.y }
140 }
141}
142
143impl From<&Vec2Serializable> for Vec2 {
144 fn from(vec: &Vec2Serializable) -> Vec2 {
145 vec2(vec.x, vec.y)
146 }
147}
148
149#[derive(Copy, Clone, PartialEq, Debug)]
150#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
152pub struct ColorCurve {
153 #[cfg_attr(feature = "nanoserde", nserde(proxy = "ColorSerializable"))]
154 #[cfg_attr(feature = "serde", serde(with = "ColorDef"))]
155 pub start: Color,
156 #[cfg_attr(feature = "nanoserde", nserde(proxy = "ColorSerializable"))]
157 #[cfg_attr(feature = "serde", serde(with = "ColorDef"))]
158 pub mid: Color,
159 #[cfg_attr(feature = "nanoserde", nserde(proxy = "ColorSerializable"))]
160 #[cfg_attr(feature = "serde", serde(with = "ColorDef"))]
161 pub end: Color,
162}
163
164impl Default for ColorCurve {
165 fn default() -> ColorCurve {
166 ColorCurve {
167 start: WHITE,
168 mid: WHITE,
169 end: WHITE,
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
175#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
176#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
177pub struct EmitterConfig {
178 pub local_coords: bool,
181 pub emission_shape: EmissionShape,
183 pub one_shot: bool,
185 pub lifetime: f32,
187 pub lifetime_randomness: f32,
190 pub explosiveness: f32,
194 pub amount: u32,
196 pub shape: ParticleShape,
198 pub emitting: bool,
201 #[cfg_attr(feature = "nanoserde", nserde(proxy = "Vec2Serializable"))]
203 #[cfg_attr(feature = "serde", serde(with = "vec2_def"))]
204 pub initial_direction: Vec2,
205 pub initial_direction_spread: f32,
207 pub initial_velocity: f32,
210 pub initial_velocity_randomness: f32,
213 pub linear_accel: f32,
215
216 pub size: f32,
218 pub size_randomness: f32,
220 #[cfg_attr(
222 feature = "serde",
223 serde(default, skip_serializing_if = "Option::is_none")
224 )]
225 pub size_curve: Option<Curve>,
226
227 pub blend_mode: BlendMode,
229
230 pub colors_curve: ColorCurve,
232
233 #[cfg_attr(feature = "nanoserde", nserde(proxy = "Vec2Serializable"))]
235 #[cfg_attr(feature = "serde", serde(with = "vec2_def"))]
236 pub gravity: Vec2,
237
238 #[cfg_attr(feature = "nanoserde", nserde(skip))]
240 #[cfg_attr(feature = "serde", serde(skip, default))]
241 pub texture: Option<Texture2D>,
242
243 #[cfg_attr(
246 feature = "serde",
247 serde(default, skip_serializing_if = "Option::is_none")
248 )]
249 pub atlas: Option<AtlasConfig>,
250
251 #[cfg_attr(
253 feature = "serde",
254 serde(default, skip_serializing_if = "Option::is_none")
255 )]
256 pub material: Option<ParticleMaterial>,
257
258 #[cfg_attr(
264 feature = "serde",
265 serde(
266 default,
267 with = "post_processing_opt",
268 skip_serializing_if = "Option::is_none"
269 )
270 )]
271 pub post_processing: Option<PostProcessing>,
272}
273
274impl EmissionShape {
275 fn gen_random_point(&self) -> Vec2 {
276 match self {
277 EmissionShape::Point => vec2(0., 0.),
278 EmissionShape::Rect { width, height } => vec2(
279 rand::gen_range(-width / 2., width / 2.0),
280 rand::gen_range(-height / 2., height / 2.0),
281 ),
282 EmissionShape::Sphere { radius } => {
283 let ro = rand::gen_range(0., radius * radius).sqrt();
284 let phi = rand::gen_range(0., std::f32::consts::PI * 2.);
285
286 macroquad::math::polar_to_cartesian(ro, phi)
287 }
288 }
289 }
290}
291
292#[derive(Copy, Clone, PartialEq, Debug)]
293#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
294#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
295pub struct PostProcessing;
296
297#[derive(Clone, PartialEq, Debug)]
298#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
299#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
300pub enum ParticleShape {
301 Rectangle,
302 Circle {
303 subdivisions: u32,
304 },
305 CustomMesh {
306 vertices: Vec<f32>,
307 indices: Vec<u16>,
308 },
309}
310
311impl ParticleShape {
312 fn build_bindings(
313 &self,
314 ctx: &mut miniquad::Context,
315 positions_vertex_buffer: Buffer,
316 texture: Option<Texture2D>,
317 ) -> Bindings {
318 let (geometry_vertex_buffer, index_buffer) = match self {
319 ParticleShape::Rectangle => {
320 #[rustfmt::skip]
321 let vertices: &[f32] = &[
322 -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0,
324 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0,
325 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
326 -1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
327 ];
328
329 let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices);
330
331 #[rustfmt::skip]
332 let indices: &[u16] = &[
333 0, 1, 2, 0, 2, 3
334 ];
335 let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
336
337 (vertex_buffer, index_buffer)
338 }
339 ParticleShape::Circle { subdivisions } => {
340 let mut vertices = Vec::<f32>::new();
341 let mut indices = Vec::<u16>::new();
342
343 let rot = 0.0;
344 vertices.extend_from_slice(&[0., 0., 0., 0., 0., 1.0, 1.0, 1.0, 1.0]);
345 for i in 0..subdivisions + 1 {
346 let rx =
347 (i as f32 / *subdivisions as f32 * std::f32::consts::PI * 2. + rot).cos();
348 let ry =
349 (i as f32 / *subdivisions as f32 * std::f32::consts::PI * 2. + rot).sin();
350 vertices.extend_from_slice(&[rx, ry, 0., rx, ry, 1., 1., 1., 1.]);
351
352 if i != *subdivisions {
353 indices.extend_from_slice(&[0, i as u16 + 1, i as u16 + 2]);
354 }
355 }
356
357 let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices);
358 let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
359 (vertex_buffer, index_buffer)
360 }
361 ParticleShape::CustomMesh { vertices, indices } => {
362 let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices);
363 let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
364 (vertex_buffer, index_buffer)
365 }
366 };
367
368 Bindings {
369 vertex_buffers: vec![geometry_vertex_buffer, positions_vertex_buffer],
370 index_buffer,
371 images: vec![texture.map_or_else(
372 || Texture::from_rgba8(ctx, 1, 1, &[255, 255, 255, 255]),
373 |texture| texture.raw_miniquad_texture_handle(),
374 )],
375 }
376 }
377}
378
379#[derive(Debug, Clone)]
380#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
381#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
382pub struct ParticleMaterial {
383 vertex: String,
384 fragment: String,
385}
386
387impl ParticleMaterial {
388 pub fn new(vertex: &str, fragment: &str) -> ParticleMaterial {
389 ParticleMaterial {
390 vertex: vertex.to_owned(),
391 fragment: fragment.to_owned(),
392 }
393 }
394}
395
396#[derive(Debug, Clone, Copy, PartialEq)]
397#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
398#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
399pub enum BlendMode {
400 Alpha,
402 Additive,
404}
405
406impl BlendMode {
407 fn blend_state(&self) -> BlendState {
408 match self {
409 BlendMode::Alpha => BlendState::new(
410 Equation::Add,
411 BlendFactor::Value(BlendValue::SourceAlpha),
412 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
413 ),
414 BlendMode::Additive => BlendState::new(
415 Equation::Add,
416 BlendFactor::Value(BlendValue::SourceAlpha),
417 BlendFactor::One,
418 ),
419 }
420 }
421}
422
423#[derive(Debug, Clone)]
424#[cfg_attr(feature = "nanoserde", derive(DeJson, SerJson))]
425#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
426pub struct AtlasConfig {
427 n: u16,
428 m: u16,
429 start_index: u16,
430 end_index: u16,
431}
432
433impl AtlasConfig {
434 pub fn new<T: std::ops::RangeBounds<u16>>(n: u16, m: u16, range: T) -> AtlasConfig {
435 let start_index = match range.start_bound() {
436 std::ops::Bound::Unbounded => 0,
437 std::ops::Bound::Included(i) => *i,
438 std::ops::Bound::Excluded(i) => i + 1,
439 };
440 let end_index = match range.end_bound() {
441 std::ops::Bound::Unbounded => n * m,
442 std::ops::Bound::Included(i) => i - 1,
443 std::ops::Bound::Excluded(i) => *i,
444 };
445
446 AtlasConfig {
447 n,
448 m,
449 start_index,
450 end_index,
451 }
452 }
453}
454
455impl Default for EmitterConfig {
456 fn default() -> EmitterConfig {
457 EmitterConfig {
458 local_coords: false,
459 emission_shape: EmissionShape::Point,
460 one_shot: false,
461 lifetime: 1.0,
462 lifetime_randomness: 0.0,
463 amount: 8,
464 shape: ParticleShape::Rectangle,
465 explosiveness: 0.0,
466 emitting: true,
467 initial_direction: vec2(0., -1.),
468 initial_direction_spread: 0.,
469 initial_velocity: 50.0,
470 initial_velocity_randomness: 0.0,
471 linear_accel: 0.0,
472 size: 10.0,
473 size_randomness: 0.0,
474 size_curve: None,
475 blend_mode: BlendMode::Alpha,
476 colors_curve: ColorCurve::default(),
477 gravity: vec2(0.0, 0.0),
478 texture: None,
479 atlas: None,
480 material: None,
481 post_processing: None,
482 }
483 }
484}
485
486#[repr(C)]
487struct GpuParticle {
488 pos: Vec4,
489 uv: Vec4,
490 data: Vec4,
491 color: Vec4,
492}
493
494struct CpuParticle {
495 velocity: Vec2,
496 lived: f32,
497 lifetime: f32,
498 frame: u16,
499 initial_size: f32,
500}
501
502pub struct Emitter {
503 pipeline: Pipeline,
504 bindings: Bindings,
505 post_processing_pass: RenderPass,
506 post_processing_pipeline: Pipeline,
507 post_processing_bindings: Bindings,
508
509 gpu_particles: Vec<GpuParticle>,
510 cpu_counterpart: Vec<CpuParticle>,
511
512 last_emit_time: f32,
513 time_passed: f32,
514
515 particles_spawned: u64,
516 position: Vec2,
517
518 batched_size_curve: Option<BatchedCurve>,
519
520 blend_mode: BlendMode,
521 mesh_dirty: bool,
522
523 pub config: EmitterConfig,
524}
525
526impl Emitter {
527 const MAX_PARTICLES: usize = 10000;
528
529 pub fn new(config: EmitterConfig) -> Emitter {
530 let InternalGlContext {
531 quad_context: ctx, ..
532 } = unsafe { get_internal_gl() };
533
534 let positions_vertex_buffer = Buffer::stream(
536 ctx,
537 BufferType::VertexBuffer,
538 Self::MAX_PARTICLES * std::mem::size_of::<Vec3>(),
539 );
540
541 let bindings = config
542 .shape
543 .build_bindings(ctx, positions_vertex_buffer, config.texture);
544
545 let (vertex, fragment) = config.material.as_ref().map_or_else(
546 || (shader::VERTEX, shader::FRAGMENT),
547 |material| (&material.vertex, &material.fragment),
548 );
549
550 let shader = {
551 use macroquad::material::shaders::{preprocess_shader, PreprocessorConfig};
552
553 let config = PreprocessorConfig {
554 includes: vec![(
555 "particles.glsl".to_string(),
556 include_str!("particles.glsl").to_owned(),
557 )],
558 ..Default::default()
559 };
560
561 let vertex = preprocess_shader(&vertex, &config);
562 let fragment = preprocess_shader(&fragment, &config);
563
564 Shader::new(ctx, &vertex, &fragment, shader::meta()).unwrap()
565 };
566
567 let blend_mode = config.blend_mode.blend_state();
568 let pipeline = Pipeline::with_params(
569 ctx,
570 &[
571 BufferLayout::default(),
572 BufferLayout {
573 step_func: VertexStep::PerInstance,
574 ..Default::default()
575 },
576 ],
577 &[
578 VertexAttribute::with_buffer("in_attr_pos", VertexFormat::Float3, 0),
579 VertexAttribute::with_buffer("in_attr_uv", VertexFormat::Float2, 0),
580 VertexAttribute::with_buffer("in_attr_color", VertexFormat::Float4, 0),
581 VertexAttribute::with_buffer("in_attr_inst_pos", VertexFormat::Float4, 1),
582 VertexAttribute::with_buffer("in_attr_inst_uv", VertexFormat::Float4, 1),
583 VertexAttribute::with_buffer("in_attr_inst_data", VertexFormat::Float4, 1),
584 VertexAttribute::with_buffer("in_attr_inst_color", VertexFormat::Float4, 1),
585 ],
586 shader,
587 PipelineParams {
588 color_blend: Some(blend_mode),
589 ..Default::default()
590 },
591 );
592
593 let post_processing_shader = Shader::new(
594 ctx,
595 &post_processing_shader::VERTEX,
596 &post_processing_shader::FRAGMENT,
597 post_processing_shader::meta(),
598 )
599 .unwrap();
600
601 let post_processing_pipeline = Pipeline::with_params(
602 ctx,
603 &[BufferLayout::default(), BufferLayout::default()],
604 &[
605 VertexAttribute::with_buffer("pos", VertexFormat::Float2, 0),
606 VertexAttribute::with_buffer("uv", VertexFormat::Float2, 0),
607 ],
608 post_processing_shader,
609 PipelineParams {
610 color_blend: Some(BlendState::new(
611 Equation::Add,
612 BlendFactor::Value(BlendValue::SourceAlpha),
613 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
614 )),
615 ..Default::default()
616 },
617 );
618 let post_processing_pass = {
619 let color_img = Texture::new_render_texture(
620 ctx,
621 TextureParams {
622 width: 320,
623 height: 200,
624 format: TextureFormat::RGBA8,
625 ..Default::default()
626 },
627 );
628 color_img.set_filter(ctx, FilterMode::Nearest);
629
630 RenderPass::new(ctx, color_img, None)
631 };
632
633 let post_processing_bindings = {
634 #[rustfmt::skip]
635 let vertices: &[f32] = &[
636 -1.0, -1.0, 0.0, 0.0,
638 1.0, -1.0, 1.0, 0.0,
639 1.0, 1.0, 1.0, 1.0,
640 -1.0, 1.0, 0.0, 1.0,
641 ];
642
643 let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices);
644
645 #[rustfmt::skip]
646 let indices: &[u16] = &[
647 0, 1, 2, 0, 2, 3
648 ];
649 let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
650 Bindings {
651 vertex_buffers: vec![vertex_buffer],
652 index_buffer,
653 images: vec![post_processing_pass.texture(ctx)],
654 }
655 };
656
657 Emitter {
658 blend_mode: config.blend_mode.clone(),
659 batched_size_curve: config.size_curve.as_ref().map(|curve| curve.batch()),
660 post_processing_pass,
661 post_processing_pipeline,
662 post_processing_bindings,
663 config,
664 pipeline,
665 bindings,
666 position: vec2(0.0, 0.0),
667 gpu_particles: Vec::with_capacity(Self::MAX_PARTICLES),
668 cpu_counterpart: Vec::with_capacity(Self::MAX_PARTICLES),
669 particles_spawned: 0,
670 last_emit_time: 0.0,
671 time_passed: 0.0,
672 mesh_dirty: false,
673 }
674 }
675
676 fn reset(&mut self) {
677 self.gpu_particles.clear();
678 self.cpu_counterpart.clear();
679 self.last_emit_time = 0.0;
680 self.time_passed = 0.0;
681 self.particles_spawned = 0;
682 }
683 pub fn rebuild_size_curve(&mut self) {
684 self.batched_size_curve = self.config.size_curve.as_ref().map(|curve| curve.batch());
685 }
686
687 pub fn update_particle_mesh(&mut self) {
688 self.mesh_dirty = true;
689 }
690
691 fn emit_particle(&mut self, offset: Vec2) {
692 let offset = offset + self.config.emission_shape.gen_random_point();
693
694 fn random_initial_vector(dir: Vec2, spread: f32, velocity: f32) -> Vec2 {
695 let angle = rand::gen_range(-spread / 2.0, spread / 2.0);
696
697 let quat = glam::Quat::from_rotation_z(angle);
698 let dir = quat * vec3(dir.x, dir.y, 0.0);
699 let res = dir * velocity;
700
701 vec2(res.x, res.y)
702 }
703
704 let r =
705 self.config.size - self.config.size * rand::gen_range(0.0, self.config.size_randomness);
706
707 let particle = if self.config.local_coords {
708 GpuParticle {
709 pos: vec4(offset.x, offset.y, 0.0, r),
710 uv: vec4(1.0, 1.0, 0.0, 0.0),
711 data: vec4(self.particles_spawned as f32, 0.0, 0.0, 0.0),
712 color: self.config.colors_curve.start.to_vec(),
713 }
714 } else {
715 GpuParticle {
716 pos: vec4(
717 self.position.x + offset.x,
718 self.position.y + offset.y,
719 0.0,
720 r,
721 ),
722 uv: vec4(1.0, 1.0, 0.0, 0.0),
723 data: vec4(self.particles_spawned as f32, 0.0, 0.0, 0.0),
724 color: self.config.colors_curve.start.to_vec(),
725 }
726 };
727
728 self.particles_spawned += 1;
729 self.gpu_particles.push(particle);
730 self.cpu_counterpart.push(CpuParticle {
731 velocity: random_initial_vector(
732 vec2(
733 self.config.initial_direction.x,
734 self.config.initial_direction.y,
735 ),
736 self.config.initial_direction_spread,
737 self.config.initial_velocity
738 - self.config.initial_velocity
739 * rand::gen_range(0.0, self.config.initial_velocity_randomness),
740 ),
741 lived: 0.0,
742 lifetime: self.config.lifetime
743 - self.config.lifetime * rand::gen_range(0.0, self.config.lifetime_randomness),
744 frame: 0,
745 initial_size: r,
746 });
747 }
748
749 fn update(&mut self, ctx: &mut Context, dt: f32) {
750 if self.mesh_dirty {
751 self.bindings = self.config.shape.build_bindings(
752 ctx,
753 self.bindings.vertex_buffers[1],
754 self.config.texture,
755 );
756 self.mesh_dirty = false;
757 }
758 if self.config.emitting {
759 self.time_passed += dt;
760
761 let gap = (self.config.lifetime / self.config.amount as f32)
762 * (1.0 - self.config.explosiveness);
763
764 let spawn_amount = if gap < 0.001 {
765 self.config.amount as usize
767 } else {
768 ((self.time_passed - self.last_emit_time) / gap) as usize
770 };
771
772 for _ in 0..spawn_amount {
773 self.last_emit_time = self.time_passed;
774
775 if self.particles_spawned < self.config.amount as u64 {
776 self.emit_particle(vec2(0.0, 0.0));
777 }
778
779 if self.gpu_particles.len() >= self.config.amount as usize {
780 break;
781 }
782 }
783 }
784
785 if self.config.one_shot && self.time_passed > self.config.lifetime {
786 self.time_passed = 0.0;
787 self.last_emit_time = 0.0;
788 self.config.emitting = false;
789 }
790
791 for (gpu, cpu) in self.gpu_particles.iter_mut().zip(&mut self.cpu_counterpart) {
792 cpu.velocity += cpu.velocity * self.config.linear_accel * dt;
795
796 gpu.color = {
797 let t = cpu.lived / cpu.lifetime;
798 if t < 0.5 {
799 let t = t * 2.;
800 self.config.colors_curve.start.to_vec() * (1.0 - t)
801 + self.config.colors_curve.mid.to_vec() * t
802 } else {
803 let t = (t - 0.5) * 2.;
804 self.config.colors_curve.mid.to_vec() * (1.0 - t)
805 + self.config.colors_curve.end.to_vec() * t
806 }
807 };
808 gpu.pos += vec4(cpu.velocity.x, cpu.velocity.y, 0.0, 0.0) * dt;
809
810 gpu.pos.w = cpu.initial_size
811 * self
812 .batched_size_curve
813 .as_ref()
814 .map_or(1.0, |curve| curve.get(cpu.lived / cpu.lifetime));
815
816 if cpu.lifetime != 0.0 {
817 gpu.data.y = cpu.lived / cpu.lifetime;
818 }
819
820 cpu.lived += dt;
821
822 cpu.velocity += self.config.gravity * dt;
823
824 if let Some(atlas) = &self.config.atlas {
825 if cpu.lifetime != 0.0 {
826 cpu.frame = (cpu.lived / cpu.lifetime
827 * (atlas.end_index - atlas.start_index) as f32)
828 as u16
829 + atlas.start_index;
830 }
831
832 let x = cpu.frame % atlas.n;
833 let y = cpu.frame / atlas.m;
834
835 gpu.uv = vec4(
836 x as f32 / atlas.n as f32,
837 y as f32 / atlas.m as f32,
838 1.0 / atlas.n as f32,
839 1.0 / atlas.m as f32,
840 );
841 } else {
842 gpu.uv = vec4(0.0, 0.0, 1.0, 1.0);
843 }
844 }
845
846 for i in (0..self.gpu_particles.len()).rev() {
847 if self.cpu_counterpart[i].lived > self.cpu_counterpart[i].lifetime
850 || self.cpu_counterpart[i].lived > self.config.lifetime
851 {
852 self.gpu_particles.remove(i);
853 self.cpu_counterpart.remove(i);
854 self.particles_spawned -= 1;
855 }
856 }
857
858 self.bindings.vertex_buffers[1].update(ctx, &self.gpu_particles[..]);
859 }
860
861 pub fn emit(&mut self, pos: Vec2, n: usize) {
863 for _ in 0..n {
864 self.emit_particle(pos);
865 self.particles_spawned += 1;
866 }
867 }
868
869 fn perform_render_pass(&mut self, quad_gl: &QuadGl, ctx: &mut Context) {
870 ctx.apply_bindings(&self.bindings);
871 ctx.apply_uniforms(&shader::Uniforms {
872 mvp: quad_gl.get_projection_matrix(),
873 emitter_position: vec3(self.position.x, self.position.y, 0.0),
874 local_coords: if self.config.local_coords { 1.0 } else { 0.0 },
875 });
876
877 ctx.draw(
878 0,
879 self.bindings.index_buffer.size() as i32 / std::mem::size_of::<u16>() as i32,
880 self.gpu_particles.len() as i32,
881 );
882 }
883
884 pub fn setup_render_pass(&mut self, quad_gl: &QuadGl, ctx: &mut Context) {
885 if self.config.blend_mode != self.blend_mode {
886 self.pipeline
887 .set_blend(ctx, Some(self.config.blend_mode.blend_state()));
888 self.blend_mode = self.config.blend_mode.clone();
889 }
890
891 if self.config.post_processing.is_none() {
892 let pass = quad_gl.get_active_render_pass();
893 if let Some(pass) = pass {
894 ctx.begin_pass(pass, PassAction::Nothing);
895 } else {
896 ctx.begin_default_pass(PassAction::Nothing);
897 }
898 } else {
899 ctx.begin_pass(
900 self.post_processing_pass,
901 PassAction::clear_color(0.0, 0.0, 0.0, 0.0),
902 );
903 };
904
905 ctx.apply_pipeline(&self.pipeline);
906 }
907
908 pub fn end_render_pass(&mut self, quad_gl: &QuadGl, ctx: &mut Context) {
909 ctx.end_render_pass();
910
911 if self.config.post_processing.is_some() {
912 let pass = quad_gl.get_active_render_pass();
913 if let Some(pass) = pass {
914 ctx.begin_pass(pass, PassAction::Nothing);
915 } else {
916 ctx.begin_default_pass(PassAction::Nothing);
917 }
918
919 ctx.apply_pipeline(&self.post_processing_pipeline);
920 let (x, y, w, h) = quad_gl.get_viewport();
921 ctx.apply_viewport(x, y, w, h);
922
923 ctx.apply_bindings(&self.post_processing_bindings);
924
925 ctx.draw(0, 6, 1);
926
927 ctx.end_render_pass();
928 }
929 }
930
931 pub fn draw(&mut self, pos: Vec2) {
932 let mut gl = unsafe { get_internal_gl() };
933
934 gl.flush();
935
936 let InternalGlContext {
937 quad_context: ctx,
938 quad_gl,
939 } = gl;
940
941 self.position = pos;
942
943 self.update(ctx, get_frame_time());
944
945 self.setup_render_pass(quad_gl, ctx);
946 self.perform_render_pass(quad_gl, ctx);
947 self.end_render_pass(quad_gl, ctx);
948 }
949}
950
951pub struct EmittersCache {
955 emitter: Emitter,
956 emitters_cache: Vec<Emitter>,
957 active_emitters: Vec<Option<(Emitter, Vec2)>>,
958 config: EmitterConfig,
959}
960
961impl EmittersCache {
962 const CACHE_DEFAULT_SIZE: usize = 10;
963
964 pub fn new(config: EmitterConfig) -> EmittersCache {
965 let mut emitters_cache = vec![];
966 for _ in 0..Self::CACHE_DEFAULT_SIZE {
968 emitters_cache.push(Emitter::new(EmitterConfig {
969 emitting: false,
970 ..config.clone()
971 }));
972 }
973 EmittersCache {
974 emitter: Emitter::new(config.clone()),
975 emitters_cache,
976 active_emitters: vec![],
977 config,
978 }
979 }
980
981 pub fn spawn(&mut self, pos: Vec2) {
982 let mut emitter = if let Some(emitter) = self.emitters_cache.pop() {
983 emitter
984 } else {
985 Emitter::new(self.config.clone())
986 };
987
988 emitter.mesh_dirty = true;
989 emitter.config.emitting = true;
990 emitter.reset();
991
992 self.active_emitters.push(Some((emitter, pos)));
993 }
994
995 pub fn draw(&mut self) {
996 let mut gl = unsafe { get_internal_gl() };
997
998 gl.flush();
999
1000 let InternalGlContext {
1001 quad_context: ctx,
1002 quad_gl,
1003 } = gl;
1004
1005 if self.active_emitters.len() > 0 {
1006 self.emitter.setup_render_pass(quad_gl, ctx);
1007 }
1008 for i in &mut self.active_emitters {
1009 if let Some((emitter, pos)) = i {
1010 emitter.position = *pos;
1011
1012 emitter.update(ctx, get_frame_time());
1013
1014 emitter.perform_render_pass(quad_gl, ctx);
1015
1016 if emitter.config.emitting == false {
1017 self.emitters_cache.push(i.take().unwrap().0);
1018 }
1019 }
1020 }
1021 if self.active_emitters.len() > 0 {
1022 self.emitter.end_render_pass(quad_gl, ctx);
1023 }
1024
1025 self.active_emitters.retain(|emitter| emitter.is_some())
1026 }
1027}
1028
1029mod shader {
1030 use super::*;
1031
1032 pub const VERTEX: &str = r#"#version 100
1033 #define DEF_VERTEX_ATTRIBUTES
1034 #include "particles.glsl"
1035
1036 varying lowp vec2 texcoord;
1037 varying lowp vec4 color;
1038
1039 void main() {
1040 gl_Position = particle_transform_vertex();
1041 color = in_attr_inst_color;
1042 texcoord = particle_transform_uv();
1043 }
1044 "#;
1045
1046 pub const FRAGMENT: &str = r#"#version 100
1047 varying lowp vec2 texcoord;
1048 varying lowp vec4 color;
1049
1050 uniform sampler2D texture;
1051
1052 void main() {
1053 gl_FragColor = texture2D(texture, texcoord) * color;
1054 }
1055 "#;
1056
1057 pub fn meta() -> ShaderMeta {
1058 ShaderMeta {
1059 images: vec!["texture".to_string()],
1060 uniforms: UniformBlockLayout {
1061 uniforms: vec![
1062 UniformDesc::new("_mvp", UniformType::Mat4),
1063 UniformDesc::new("_local_coords", UniformType::Float1),
1064 UniformDesc::new("_emitter_position", UniformType::Float3),
1065 ],
1066 },
1067 }
1068 }
1069
1070 #[repr(C)]
1071 pub struct Uniforms {
1072 pub mvp: Mat4,
1073 pub local_coords: f32,
1074 pub emitter_position: Vec3,
1075 }
1076}
1077
1078mod post_processing_shader {
1079 use super::*;
1080
1081 pub const VERTEX: &str = r#"#version 100
1082 attribute vec2 pos;
1083 attribute vec2 uv;
1084
1085 varying lowp vec2 texcoord;
1086
1087 void main() {
1088 gl_Position = vec4(pos, 0, 1);
1089 texcoord = uv;
1090 }
1091 "#;
1092
1093 pub const FRAGMENT: &str = r#"#version 100
1094 precision lowp float;
1095
1096 varying vec2 texcoord;
1097 uniform sampler2D tex;
1098
1099 void main() {
1100 gl_FragColor = texture2D(tex, texcoord);
1101 }
1102 "#;
1103
1104 pub fn meta() -> ShaderMeta {
1105 ShaderMeta {
1106 images: vec!["tex".to_string()],
1107 uniforms: UniformBlockLayout { uniforms: vec![] },
1108 }
1109 }
1110}