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::{Graphics, GraphicsBatch},
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>>(&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 wireframe: context.wireframe,
188 };
189 graphics.stream.batch_optimized(batch);
190 let parent = context.top_transform();
191 for instance in instances {
192 let transform = parent * transform_to_matrix(instance.transform);
193 let offset = instance.size * instance.pivot;
194 let color = instance.tint.into_array();
195 graphics.stream.transformed(
196 |stream| {
197 stream.quad([
198 Vertex {
199 position: [0.0, 0.0],
200 uv: [instance.region.x, instance.region.y, instance.page],
201 color,
202 },
203 Vertex {
204 position: [instance.size.x, 0.0],
205 uv: [
206 instance.region.x + instance.region.w,
207 instance.region.y,
208 instance.page,
209 ],
210 color,
211 },
212 Vertex {
213 position: [instance.size.x, instance.size.y],
214 uv: [
215 instance.region.x + instance.region.w,
216 instance.region.y + instance.region.h,
217 instance.page,
218 ],
219 color,
220 },
221 Vertex {
222 position: [0.0, instance.size.y],
223 uv: [
224 instance.region.x,
225 instance.region.y + instance.region.h,
226 instance.page,
227 ],
228 color,
229 },
230 ]);
231 },
232 |vertex| {
233 let point = transform.mul_point(Vec2::from(vertex.position) - offset);
234 vertex.position[0] = point.x;
235 vertex.position[1] = point.y;
236 },
237 );
238 }
239 }
240}
241
242pub trait ParticleSystemProcessor<D, C> {
243 fn process(config: &C, data: D) -> Option<D>;
244 fn emit(config: &C, data: &D) -> Option<ParticleInstance>;
245}
246
247pub struct ParticleSystem<P: ParticleSystemProcessor<D, C>, D, C> {
248 pub config: C,
249 source: Vec<D>,
250 target: Vec<D>,
251 _phantom: PhantomData<fn() -> P>,
252}
253
254impl<P: ParticleSystemProcessor<D, C>, D, C> ParticleSystem<P, D, C> {
255 pub fn new(config: C, capacity: usize) -> Self {
256 Self {
257 config,
258 source: Vec::with_capacity(capacity),
259 target: Vec::with_capacity(capacity),
260 _phantom: Default::default(),
261 }
262 }
263
264 pub fn len(&self) -> usize {
265 self.source.len()
266 }
267
268 pub fn is_empty(&self) -> bool {
269 self.source.is_empty()
270 }
271
272 pub fn push(&mut self, data: D) {
273 if self.source.len() < self.source.capacity() {
274 self.source.push(data);
275 }
276 }
277
278 pub fn extend(&mut self, iter: impl IntoIterator<Item = D>) {
279 self.source.extend(iter);
280 }
281
282 pub fn clear(&mut self) {
283 self.source.clear();
284 self.target.clear();
285 }
286
287 pub fn process(&mut self) {
288 self.target.clear();
289 self.target.reserve(self.source.len());
290 for item in self.source.drain(..) {
291 if let Some(item) = P::process(&self.config, item) {
292 self.target.push(item);
293 }
294 }
295 std::mem::swap(&mut self.source, &mut self.target);
296 }
297
298 pub fn emit(&self) -> impl Iterator<Item = ParticleInstance> + '_ {
299 self.source
300 .iter()
301 .filter_map(|item| P::emit(&self.config, item))
302 }
303}
304
305impl<P: ParticleSystemProcessor<D, C>, D: std::fmt::Debug, C: std::fmt::Debug> std::fmt::Debug
306 for ParticleSystem<P, D, C>
307{
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 f.debug_struct("ParticleSystem")
310 .field("config", &self.config)
311 .field("data", &self.source)
312 .finish_non_exhaustive()
313 }
314}