spitfire_draw/
primitives.rs

1use crate::{
2    context::DrawContext,
3    sprite::SpriteTexture,
4    utils::{Drawable, ShaderRef, Vertex},
5};
6use smallvec::SmallVec;
7use spitfire_core::{Triangle, VertexStream};
8use spitfire_glow::{
9    graphics::{Graphics, GraphicsBatch},
10    renderer::{GlowBlending, GlowUniformValue},
11};
12use std::{
13    borrow::Cow,
14    cell::RefCell,
15    collections::HashMap,
16    f32::consts::{PI, TAU},
17};
18use vek::{Mat4, Rect, Rgba, Vec2};
19
20#[derive(Debug, Default, Clone)]
21pub struct PrimitivesEmitter {
22    pub shader: Option<ShaderRef>,
23    pub textures: SmallVec<[SpriteTexture; 4]>,
24    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
25    pub blending: Option<GlowBlending>,
26    pub screen_space: bool,
27}
28
29impl PrimitivesEmitter {
30    pub fn single(texture: SpriteTexture) -> Self {
31        Self {
32            textures: vec![texture].into(),
33            ..Default::default()
34        }
35    }
36
37    pub fn shader(mut self, value: ShaderRef) -> Self {
38        self.shader = Some(value);
39        self
40    }
41
42    pub fn texture(mut self, value: SpriteTexture) -> Self {
43        self.textures.push(value);
44        self
45    }
46
47    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
48        self.uniforms.insert(key, value);
49        self
50    }
51
52    pub fn blending(mut self, value: GlowBlending) -> Self {
53        self.blending = Some(value);
54        self
55    }
56
57    pub fn screen_space(mut self, value: bool) -> Self {
58        self.screen_space = value;
59        self
60    }
61
62    pub fn emit_lines<I: IntoIterator<Item = Vec2<f32>>>(&self, vertices: I) -> LinesDraw<I> {
63        LinesDraw {
64            emitter: self,
65            vertices: RefCell::new(Some(vertices)),
66            region: Rect {
67                x: 0.0,
68                y: 0.0,
69                w: 1.0,
70                h: 1.0,
71            },
72            page: 0.0,
73            tint: Rgba::white(),
74            thickness: 1.0,
75            looped: false,
76        }
77    }
78
79    pub fn emit_brush<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>>(
80        &self,
81        vertices: I,
82    ) -> BrushDraw<I> {
83        BrushDraw {
84            emitter: self,
85            vertices: RefCell::new(Some(vertices)),
86            region: Rect {
87                x: 0.0,
88                y: 0.0,
89                w: 1.0,
90                h: 1.0,
91            },
92            page: 0.0,
93        }
94    }
95
96    pub fn emit_triangles<I: IntoIterator<Item = [Vertex; 3]>>(
97        &self,
98        vertices: I,
99    ) -> TrianglesDraw<I> {
100        TrianglesDraw {
101            emitter: self,
102            vertices: RefCell::new(Some(vertices)),
103            tint: Rgba::white(),
104        }
105    }
106
107    pub fn emit_triangle_fan<I: IntoIterator<Item = Vertex>>(
108        &self,
109        vertices: I,
110    ) -> TriangleFanDraw<I> {
111        TriangleFanDraw {
112            emitter: self,
113            vertices: RefCell::new(Some(vertices)),
114        }
115    }
116
117    pub fn emit_triangle_strip<I: IntoIterator<Item = Vertex>>(
118        &self,
119        vertices: I,
120    ) -> TriangleStripDraw<I> {
121        TriangleStripDraw {
122            emitter: self,
123            vertices: RefCell::new(Some(vertices)),
124        }
125    }
126
127    pub fn emit_regular_polygon(
128        &self,
129        vertices: usize,
130        position: Vec2<f32>,
131        radius: f32,
132    ) -> RegularPolygonDraw {
133        RegularPolygonDraw {
134            emitter: self,
135            vertices,
136            position,
137            radius,
138            region: Rect {
139                x: 0.0,
140                y: 0.0,
141                w: 1.0,
142                h: 1.0,
143            },
144            page: 0.0,
145            tint: Rgba::white(),
146        }
147    }
148
149    pub fn emit_circle(
150        &self,
151        position: Vec2<f32>,
152        radius: f32,
153        maximum_error: f32,
154    ) -> RegularPolygonDraw {
155        RegularPolygonDraw {
156            emitter: self,
157            vertices: (PI / (1.0 - maximum_error / radius).acos()).ceil() as _,
158            position,
159            radius,
160            region: Rect {
161                x: 0.0,
162                y: 0.0,
163                w: 1.0,
164                h: 1.0,
165            },
166            page: 0.0,
167            tint: Rgba::white(),
168        }
169    }
170
171    fn stream_transformed(
172        &self,
173        context: &mut DrawContext,
174        graphics: &mut Graphics<Vertex>,
175        f: impl FnMut(&mut VertexStream<Vertex, GraphicsBatch>),
176    ) {
177        let batch = GraphicsBatch {
178            shader: context.shader(self.shader.as_ref()),
179            uniforms: self
180                .uniforms
181                .iter()
182                .map(|(k, v)| (k.clone(), v.to_owned()))
183                .chain(std::iter::once((
184                    "u_projection_view".into(),
185                    GlowUniformValue::M4(
186                        if self.screen_space {
187                            graphics.main_camera.screen_matrix()
188                        } else {
189                            graphics.main_camera.world_matrix()
190                        }
191                        .into_col_array(),
192                    ),
193                )))
194                .chain(self.textures.iter().enumerate().map(|(index, texture)| {
195                    (texture.sampler.clone(), GlowUniformValue::I1(index as _))
196                }))
197                .collect(),
198            textures: self
199                .textures
200                .iter()
201                .filter_map(|texture| {
202                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
203                })
204                .collect(),
205            blending: self.blending.unwrap_or_else(|| context.top_blending()),
206            scissor: None,
207        };
208        graphics.stream.batch_optimized(batch);
209        let transform = Mat4::from(context.top_transform());
210        graphics.stream.transformed(f, |vertex| {
211            let point = transform.mul_point(Vec2::from(vertex.position));
212            vertex.position[0] = point.x;
213            vertex.position[1] = point.y;
214        });
215    }
216}
217
218pub struct LinesDraw<'a, I: IntoIterator<Item = Vec2<f32>>> {
219    emitter: &'a PrimitivesEmitter,
220    vertices: RefCell<Option<I>>,
221    pub region: Rect<f32, f32>,
222    pub page: f32,
223    pub tint: Rgba<f32>,
224    pub thickness: f32,
225    pub looped: bool,
226}
227
228impl<I: IntoIterator<Item = Vec2<f32>>> LinesDraw<'_, I> {
229    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
230        self.region = region;
231        self.page = page;
232        self
233    }
234
235    pub fn tint(mut self, value: Rgba<f32>) -> Self {
236        self.tint = value;
237        self
238    }
239
240    pub fn thickness(mut self, value: f32) -> Self {
241        self.thickness = value;
242        self
243    }
244
245    pub fn looped(mut self, value: bool) -> Self {
246        self.looped = value;
247        self
248    }
249}
250
251impl<I: IntoIterator<Item = Vec2<f32>>> Drawable for LinesDraw<'_, I> {
252    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
253        fn push(
254            stream: &mut VertexStream<Vertex, GraphicsBatch>,
255            region: Rect<f32, f32>,
256            page: f32,
257            color: [f32; 4],
258            prev: Vec2<f32>,
259            next: Vec2<f32>,
260            normal: Vec2<f32>,
261        ) {
262            stream.extend(
263                [
264                    Vertex {
265                        position: (prev - normal).into_array(),
266                        uv: [region.x, region.y, page],
267                        color,
268                    },
269                    Vertex {
270                        position: (prev + normal).into_array(),
271                        uv: [region.x + region.w, region.y, page],
272                        color,
273                    },
274                    Vertex {
275                        position: (next + normal).into_array(),
276                        uv: [region.x + region.w, region.y + region.h, page],
277                        color,
278                    },
279                    Vertex {
280                        position: (next - normal).into_array(),
281                        uv: [region.x, region.y + region.h, page],
282                        color,
283                    },
284                ],
285                [Triangle { a: 0, b: 1, c: 2 }, Triangle { a: 2, b: 3, c: 0 }],
286            );
287        }
288
289        self.emitter
290            .stream_transformed(context, graphics, |stream| {
291                if let Some(vertices) = self.vertices.borrow_mut().take() {
292                    let mut vertices = vertices.into_iter();
293                    let Some(mut prev) = vertices.next() else {
294                        return;
295                    };
296                    let start = prev;
297                    let color = self.tint.into_array();
298                    for next in vertices {
299                        let tangent = next - prev;
300                        let normal = Vec2 {
301                            x: tangent.y,
302                            y: -tangent.x,
303                        }
304                        .try_normalized()
305                        .unwrap_or_default()
306                            * self.thickness;
307                        push(stream, self.region, self.page, color, prev, next, normal);
308                        prev = next;
309                    }
310                    if self.looped {
311                        let tangent = start - prev;
312                        let normal = Vec2 {
313                            x: tangent.y,
314                            y: -tangent.x,
315                        }
316                        .try_normalized()
317                        .unwrap_or_default()
318                            * self.thickness;
319                        push(stream, self.region, self.page, color, prev, start, normal);
320                    }
321                }
322            });
323    }
324}
325
326pub struct BrushDraw<'a, I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> {
327    emitter: &'a PrimitivesEmitter,
328    vertices: RefCell<Option<I>>,
329    pub region: Rect<f32, f32>,
330    pub page: f32,
331}
332
333impl<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> BrushDraw<'_, I> {
334    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
335        self.region = region;
336        self.page = page;
337        self
338    }
339}
340
341impl<I: IntoIterator<Item = (Vec2<f32>, f32, Rgba<f32>)>> Drawable for BrushDraw<'_, I> {
342    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
343        fn push(
344            stream: &mut VertexStream<Vertex, GraphicsBatch>,
345            region: Rect<f32, f32>,
346            page: f32,
347            prev: (Vec2<f32>, f32, Rgba<f32>),
348            next: (Vec2<f32>, f32, Rgba<f32>),
349            normal_prev: Vec2<f32>,
350            normal_next: Vec2<f32>,
351        ) {
352            stream.extend(
353                [
354                    Vertex {
355                        position: ((prev.0 + next.0) * 0.5).into_array(),
356                        uv: [region.x, region.y, page],
357                        color: ((prev.2 + next.2) * 0.5).into_array(),
358                    },
359                    Vertex {
360                        position: (prev.0 - normal_prev * prev.1).into_array(),
361                        uv: [region.x, region.y, page],
362                        color: prev.2.into_array(),
363                    },
364                    Vertex {
365                        position: (prev.0 + normal_prev * prev.1).into_array(),
366                        uv: [region.x + region.w, region.y, page],
367                        color: prev.2.into_array(),
368                    },
369                    Vertex {
370                        position: (next.0 + normal_next * next.1).into_array(),
371                        uv: [region.x + region.w, region.y + region.h, page],
372                        color: next.2.into_array(),
373                    },
374                    Vertex {
375                        position: (next.0 - normal_next * next.1).into_array(),
376                        uv: [region.x, region.y + region.h, page],
377                        color: next.2.into_array(),
378                    },
379                ],
380                [
381                    Triangle { a: 0, b: 1, c: 2 },
382                    Triangle { a: 0, b: 2, c: 3 },
383                    Triangle { a: 0, b: 3, c: 4 },
384                    Triangle { a: 0, b: 4, c: 1 },
385                ],
386            );
387        }
388
389        self.emitter
390            .stream_transformed(context, graphics, |stream| {
391                if let Some(vertices) = self.vertices.borrow_mut().take() {
392                    let mut vertices = vertices.into_iter().peekable();
393                    let Some(mut prev) = vertices.next() else {
394                        return;
395                    };
396                    let mut prev_tangent = Option::<Vec2<f32>>::None;
397                    while let Some(curr) = vertices.next() {
398                        let next = vertices.peek().copied();
399                        let curr_tangent = (curr.0 - prev.0).try_normalized().unwrap_or_default();
400                        let tangent = prev_tangent
401                            .replace(curr_tangent)
402                            .and_then(|tangent| (curr_tangent + tangent).try_normalized())
403                            .unwrap_or(curr_tangent);
404                        let next_tangent = next
405                            .and_then(|next| (next.0 - curr.0).try_normalized())
406                            .and_then(|tangent| (curr_tangent + tangent).try_normalized())
407                            .unwrap_or(curr_tangent);
408                        let normal_prev = Vec2 {
409                            x: tangent.y,
410                            y: -tangent.x,
411                        }
412                        .try_normalized()
413                        .unwrap_or_default();
414                        let normal_next = Vec2 {
415                            x: next_tangent.y,
416                            y: -next_tangent.x,
417                        }
418                        .try_normalized()
419                        .unwrap_or_default();
420                        push(
421                            stream,
422                            self.region,
423                            self.page,
424                            prev,
425                            curr,
426                            normal_prev,
427                            normal_next,
428                        );
429                        prev = curr;
430                    }
431                }
432            });
433    }
434}
435
436pub struct TrianglesDraw<'a, I: IntoIterator<Item = [Vertex; 3]>> {
437    emitter: &'a PrimitivesEmitter,
438    vertices: RefCell<Option<I>>,
439    pub tint: Rgba<f32>,
440}
441
442impl<I: IntoIterator<Item = [Vertex; 3]>> Drawable for TrianglesDraw<'_, I> {
443    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
444        self.emitter
445            .stream_transformed(context, graphics, |stream| {
446                if let Some(vertices) = self.vertices.borrow_mut().take() {
447                    unsafe {
448                        let start = stream.vertices().len();
449                        stream.extend_vertices(vertices.into_iter().flatten());
450                        let end = stream.vertices().len();
451                        stream.extend_triangles(
452                            false,
453                            (start..end).step_by(3).map(|index| Triangle {
454                                a: index as u32,
455                                b: index as u32 + 1,
456                                c: index as u32 + 2,
457                            }),
458                        );
459                    }
460                }
461            });
462    }
463}
464
465pub struct TriangleFanDraw<'a, I: IntoIterator<Item = Vertex>> {
466    emitter: &'a PrimitivesEmitter,
467    vertices: RefCell<Option<I>>,
468}
469
470impl<I: IntoIterator<Item = Vertex>> Drawable for TriangleFanDraw<'_, I> {
471    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
472        self.emitter
473            .stream_transformed(context, graphics, |stream| {
474                if let Some(vertices) = self.vertices.borrow_mut().take() {
475                    stream.triangle_fan(vertices);
476                }
477            });
478    }
479}
480
481pub struct TriangleStripDraw<'a, I: IntoIterator<Item = Vertex>> {
482    emitter: &'a PrimitivesEmitter,
483    vertices: RefCell<Option<I>>,
484}
485
486impl<I: IntoIterator<Item = Vertex>> Drawable for TriangleStripDraw<'_, I> {
487    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
488        self.emitter
489            .stream_transformed(context, graphics, |stream| {
490                if let Some(vertices) = self.vertices.borrow_mut().take() {
491                    stream.triangle_strip(vertices);
492                }
493            });
494    }
495}
496
497pub struct RegularPolygonDraw<'a> {
498    emitter: &'a PrimitivesEmitter,
499    vertices: usize,
500    position: Vec2<f32>,
501    radius: f32,
502    pub region: Rect<f32, f32>,
503    pub page: f32,
504    pub tint: Rgba<f32>,
505}
506
507impl RegularPolygonDraw<'_> {
508    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
509        self.region = region;
510        self.page = page;
511        self
512    }
513
514    pub fn tint(mut self, value: Rgba<f32>) -> Self {
515        self.tint = value;
516        self
517    }
518}
519
520impl Drawable for RegularPolygonDraw<'_> {
521    fn draw(&self, context: &mut DrawContext, graphics: &mut Graphics<Vertex>) {
522        let color = self.tint.into_array();
523        self.emitter
524            .stream_transformed(context, graphics, |stream| {
525                stream.triangle_fan((0..=self.vertices).map(|index| {
526                    let angle = TAU / self.vertices as f32 * index as f32;
527                    let (y, x) = angle.sin_cos();
528                    let u = (x + 1.0) * 0.5;
529                    let v = (y + 1.0) * 0.5;
530                    Vertex {
531                        position: [
532                            self.position.x + x * self.radius,
533                            self.position.y + y * self.radius,
534                        ],
535                        uv: [
536                            self.region.x + self.region.w * u,
537                            self.region.y + self.region.h * v,
538                            self.page,
539                        ],
540                        color,
541                    }
542                }));
543            });
544    }
545}