spitfire_draw/
particles.rs

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