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