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}