spitfire_draw/
particles.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_glow::{
8    graphics::{Graphics, GraphicsBatch},
9    renderer::{GlowBlending, GlowUniformValue},
10};
11use std::{borrow::Cow, cell::RefCell, collections::HashMap, marker::PhantomData};
12use vek::{Mat4, Quaternion, Rect, Rgba, Transform, Vec2, Vec3};
13
14#[derive(Debug, Default, Clone)]
15pub struct ParticleEmitter {
16    pub shader: Option<ShaderRef>,
17    pub textures: SmallVec<[SpriteTexture; 4]>,
18    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
19    pub blending: Option<GlowBlending>,
20    pub screen_space: bool,
21}
22
23impl ParticleEmitter {
24    pub fn single(texture: SpriteTexture) -> Self {
25        Self {
26            textures: vec![texture].into(),
27            ..Default::default()
28        }
29    }
30
31    pub fn shader(mut self, value: ShaderRef) -> Self {
32        self.shader = Some(value);
33        self
34    }
35
36    pub fn texture(mut self, value: SpriteTexture) -> Self {
37        self.textures.push(value);
38        self
39    }
40
41    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
42        self.uniforms.insert(key, value);
43        self
44    }
45
46    pub fn blending(mut self, value: GlowBlending) -> Self {
47        self.blending = Some(value);
48        self
49    }
50
51    pub fn screen_space(mut self, value: bool) -> Self {
52        self.screen_space = value;
53        self
54    }
55
56    pub fn emit<I: IntoIterator<Item = ParticleInstance>>(&self, instances: I) -> ParticleDraw<I> {
57        ParticleDraw {
58            emitter: self,
59            instances: RefCell::new(Some(instances)),
60        }
61    }
62}
63
64#[derive(Debug, Clone)]
65pub struct ParticleInstance {
66    pub region: Rect<f32, f32>,
67    pub page: f32,
68    pub tint: Rgba<f32>,
69    pub transform: Transform<f32, f32, f32>,
70    pub size: Vec2<f32>,
71    pub pivot: Vec2<f32>,
72}
73
74impl Default for ParticleInstance {
75    fn default() -> Self {
76        Self {
77            region: Rect::new(0.0, 0.0, 1.0, 1.0),
78            page: Default::default(),
79            tint: Rgba::white(),
80            transform: Default::default(),
81            size: Default::default(),
82            pivot: Default::default(),
83        }
84    }
85}
86
87impl ParticleInstance {
88    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
89        self.region = region;
90        self.page = page;
91        self
92    }
93
94    pub fn tint(mut self, value: Rgba<f32>) -> Self {
95        self.tint = value;
96        self
97    }
98
99    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
100        self.transform = value;
101        self
102    }
103
104    pub fn position(mut self, value: Vec2<f32>) -> Self {
105        self.transform.position = value.into();
106        self
107    }
108
109    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
110        self.transform.orientation = value;
111        self
112    }
113
114    pub fn rotation(mut self, angle_radians: f32) -> Self {
115        self.transform.orientation = Quaternion::rotation_z(angle_radians);
116        self
117    }
118
119    pub fn scale(mut self, value: Vec2<f32>) -> Self {
120        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
121        self
122    }
123
124    pub fn size(mut self, value: Vec2<f32>) -> Self {
125        self.size = value;
126        self
127    }
128
129    pub fn pivot(mut self, value: Vec2<f32>) -> Self {
130        self.pivot = value;
131        self
132    }
133}
134
135pub struct ParticleDraw<'a, I: IntoIterator<Item = ParticleInstance>> {
136    emitter: &'a ParticleEmitter,
137    instances: RefCell<Option<I>>,
138}
139
140impl<I: IntoIterator<Item = ParticleInstance>> Drawable for ParticleDraw<'_, I> {
141    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
142        let instances = match self.instances.borrow_mut().take() {
143            Some(instances) => instances,
144            None => return,
145        };
146        let batch = GraphicsBatch {
147            shader: context.shader(self.emitter.shader.as_ref()),
148            uniforms: self
149                .emitter
150                .uniforms
151                .iter()
152                .map(|(k, v)| (k.clone(), v.to_owned()))
153                .chain(std::iter::once((
154                    "u_projection_view".into(),
155                    GlowUniformValue::M4(
156                        if self.emitter.screen_space {
157                            graphics.main_camera.screen_matrix()
158                        } else {
159                            graphics.main_camera.world_matrix()
160                        }
161                        .into_col_array(),
162                    ),
163                )))
164                .chain(
165                    self.emitter
166                        .textures
167                        .iter()
168                        .enumerate()
169                        .map(|(index, texture)| {
170                            (texture.sampler.clone(), GlowUniformValue::I1(index as _))
171                        }),
172                )
173                .collect(),
174            textures: self
175                .emitter
176                .textures
177                .iter()
178                .filter_map(|texture| {
179                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
180                })
181                .collect(),
182            blending: self
183                .emitter
184                .blending
185                .unwrap_or_else(|| context.top_blending()),
186            scissor: None,
187        };
188        graphics.stream.batch_optimized(batch);
189        let parent = Mat4::from(context.top_transform());
190        for instance in instances {
191            let transform = parent * Mat4::from(instance.transform);
192            let offset = instance.size * instance.pivot;
193            let color = instance.tint.into_array();
194            graphics.stream.transformed(
195                |stream| {
196                    stream.quad([
197                        Vertex {
198                            position: [0.0, 0.0],
199                            uv: [instance.region.x, instance.region.y, instance.page],
200                            color,
201                        },
202                        Vertex {
203                            position: [instance.size.x, 0.0],
204                            uv: [
205                                instance.region.x + instance.region.w,
206                                instance.region.y,
207                                instance.page,
208                            ],
209                            color,
210                        },
211                        Vertex {
212                            position: [instance.size.x, instance.size.y],
213                            uv: [
214                                instance.region.x + instance.region.w,
215                                instance.region.y + instance.region.h,
216                                instance.page,
217                            ],
218                            color,
219                        },
220                        Vertex {
221                            position: [0.0, instance.size.y],
222                            uv: [
223                                instance.region.x,
224                                instance.region.y + instance.region.h,
225                                instance.page,
226                            ],
227                            color,
228                        },
229                    ]);
230                },
231                |vertex| {
232                    let point = transform.mul_point(Vec2::from(vertex.position) - offset);
233                    vertex.position[0] = point.x;
234                    vertex.position[1] = point.y;
235                },
236            );
237        }
238    }
239}
240
241pub trait ParticleSystemProcessor<D, C> {
242    fn process(config: &C, data: D) -> Option<D>;
243    fn emit(config: &C, data: &D) -> Option<ParticleInstance>;
244}
245
246pub struct ParticleSystem<P: ParticleSystemProcessor<D, C>, D, C> {
247    pub config: C,
248    source: Vec<D>,
249    target: Vec<D>,
250    _phantom: PhantomData<fn() -> P>,
251}
252
253impl<P: ParticleSystemProcessor<D, C>, D, C> ParticleSystem<P, D, C> {
254    pub fn new(config: C, capacity: usize) -> Self {
255        Self {
256            config,
257            source: Vec::with_capacity(capacity),
258            target: Vec::with_capacity(capacity),
259            _phantom: Default::default(),
260        }
261    }
262
263    pub fn len(&self) -> usize {
264        self.source.len()
265    }
266
267    pub fn is_empty(&self) -> bool {
268        self.source.is_empty()
269    }
270
271    pub fn push(&mut self, data: D) {
272        if self.source.len() < self.source.capacity() {
273            self.source.push(data);
274        }
275    }
276
277    pub fn extend(&mut self, iter: impl IntoIterator<Item = D>) {
278        self.source.extend(iter);
279    }
280
281    pub fn clear(&mut self) {
282        self.source.clear();
283        self.target.clear();
284    }
285
286    pub fn process(&mut self) {
287        self.target.clear();
288        self.target.reserve(self.source.len());
289        for item in self.source.drain(..) {
290            if let Some(item) = P::process(&self.config, item) {
291                self.target.push(item);
292            }
293        }
294        std::mem::swap(&mut self.source, &mut self.target);
295    }
296
297    pub fn emit(&self) -> impl Iterator<Item = ParticleInstance> + '_ {
298        self.source
299            .iter()
300            .filter_map(|item| P::emit(&self.config, item))
301    }
302}
303
304impl<P: ParticleSystemProcessor<D, C>, D: std::fmt::Debug, C: std::fmt::Debug> std::fmt::Debug
305    for ParticleSystem<P, D, C>
306{
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        f.debug_struct("ParticleSystem")
309            .field("config", &self.config)
310            .field("data", &self.source)
311            .finish_non_exhaustive()
312    }
313}