Skip to main content

euv_chart/geom/tess/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`Tessellator`].
4impl Tessellator {
5    /// Returns a tessellator with the default flattening tolerance.
6    pub fn new() -> Self {
7        Self {
8            tolerance: DEFAULT_FLATTEN_TOLERANCE,
9            flat: FlatPath::default(),
10            working: Vec::new(),
11            indices: Vec::new(),
12        }
13    }
14
15    /// Returns a tessellator with the given flattening tolerance, in
16    /// pixels.
17    ///
18    /// A smaller tolerance produces smoother curves at the cost of more
19    /// vertices.
20    ///
21    /// # Arguments
22    ///
23    /// - `f64` - A 64-bit float (`f64`).
24    pub fn with_tolerance(tolerance: f64) -> Self {
25        Self {
26            tolerance,
27            ..Self::new()
28        }
29    }
30
31    /// Returns the flattening tolerance, in pixels.
32    ///
33    /// # Returns
34    ///
35    /// - `f64` - A 64-bit float.
36    pub fn tolerance(&self) -> f64 {
37        self.get_tolerance()
38    }
39
40    /// Flattens the path into contours and returns them.
41    ///
42    /// The returned reference borrows the tessellator's scratch storage and
43    /// stays valid until the next call to [`Tessellator::flatten`].
44    ///
45    /// # Arguments
46    ///
47    /// - `&Path` - Shared reference to a `Path`.
48    ///
49    /// # Returns
50    ///
51    /// - `&FlatPath` - A `FlatPath` value.
52    pub fn flatten(&mut self, path: &Path) -> &FlatPath {
53        self.get_mut_flat().contours.clear();
54        let mut current: Contour = Contour::default();
55        let mut cursor: (f64, f64) = (0.0_f64, 0.0_f64);
56        let mut sub_start: (f64, f64) = (0.0_f64, 0.0_f64);
57        for command in path.commands() {
58            match *command {
59                PathCommand::MoveTo(x, y) => {
60                    if current.points.len() > 1 {
61                        self.get_mut_flat().contours.push(take(&mut current));
62                    } else {
63                        current = Contour::default();
64                    }
65                    cursor = (x as f64, y as f64);
66                    sub_start = cursor;
67                    current.points.push(Point::new(x, y));
68                }
69                PathCommand::LineTo(x, y) => {
70                    cursor = (x as f64, y as f64);
71                    current.points.push(Point::new(x, y));
72                }
73                PathCommand::QuadTo(cx, cy, x, y) => {
74                    let count: usize = quad_segment_count(
75                        cursor,
76                        (cx as f64, cy as f64),
77                        (x as f64, y as f64),
78                        self.get_tolerance(),
79                    );
80                    for step in 1..=count {
81                        let t: f64 = step as f64 / count as f64;
82                        let (px, py) =
83                            eval_quad(cursor, (cx as f64, cy as f64), (x as f64, y as f64), t);
84                        current.points.push(Point::new(px as f32, py as f32));
85                    }
86                    cursor = (x as f64, y as f64);
87                }
88                PathCommand::CubicTo(c1x, c1y, c2x, c2y, x, y) => {
89                    let count: usize = cubic_segment_count(
90                        [
91                            cursor,
92                            (c1x as f64, c1y as f64),
93                            (c2x as f64, c2y as f64),
94                            (x as f64, y as f64),
95                        ],
96                        self.get_tolerance(),
97                    );
98                    for step in 1..=count {
99                        let t: f64 = step as f64 / count as f64;
100                        let (px, py) = eval_cubic(
101                            cursor,
102                            (c1x as f64, c1y as f64),
103                            (c2x as f64, c2y as f64),
104                            (x as f64, y as f64),
105                            t,
106                        );
107                        current.points.push(Point::new(px as f32, py as f32));
108                    }
109                    cursor = (x as f64, y as f64);
110                }
111                PathCommand::Close => {
112                    if current.points.len() > 1 {
113                        current.closed = true;
114                        self.get_mut_flat().contours.push(take(&mut current));
115                    } else {
116                        current = Contour::default();
117                    }
118                    cursor = sub_start;
119                }
120            }
121        }
122        if current.points.len() > 1 {
123            self.get_mut_flat().contours.push(current);
124        }
125        self.get_flat()
126    }
127
128    /// Triangulates the path and returns the resulting mesh.
129    ///
130    /// The path is filled according to its [`FillRule`]. Holes are detected
131    /// automatically: a contour nested inside another contour with the
132    /// opposite winding direction is treated as a hole, which is what makes
133    /// a doughnut chart's inner ring disappear.
134    ///
135    /// # Arguments
136    ///
137    /// - `&Path` - Shared reference to a `Path`.
138    /// - `u32` - A 32-bit unsigned integer (`u32`).
139    ///
140    /// # Returns
141    ///
142    /// - `SolidMesh` - A `SolidMesh` value.
143    pub fn fill(&mut self, path: &Path, color: u32) -> SolidMesh {
144        self.fill_flattened(path, color, FillRule::NonZero)
145    }
146
147    /// Triangulates the path and returns the resulting mesh, using the
148    /// given fill rule.
149    ///
150    /// # Arguments
151    ///
152    /// - `&Path` - Shared reference to a `Path`.
153    /// - `u32` - A 32-bit unsigned integer (`u32`).
154    /// - `FillRule` - A `FillRule` parameter.
155    ///
156    /// # Returns
157    ///
158    /// - `SolidMesh` - A `SolidMesh` value.
159    pub fn fill_flattened(&mut self, path: &Path, color: u32, rule: FillRule) -> SolidMesh {
160        // A rectangle is two triangles, and it is the shape a chart
161        // holds the most of. It skips the flattening and the ear
162        // clipping altogether.
163        if let Some(corners) = rectangle_of(path) {
164            let mut mesh: SolidMesh = SolidMesh::new();
165            mesh.reserve(6, 2);
166            let vertex = move |point: Point| SolidVertex {
167                x: point.x,
168                y: point.y,
169                color,
170            };
171            mesh.push_triangle(vertex(corners[0]), vertex(corners[1]), vertex(corners[2]));
172            mesh.push_triangle(vertex(corners[0]), vertex(corners[2]), vertex(corners[3]));
173            return mesh;
174        }
175        self.flatten(path);
176        let mut mesh: SolidMesh = SolidMesh::new();
177        let Self {
178            flat,
179            working,
180            indices,
181            ..
182        } = self;
183        fill_contours_into(&mut mesh, &flat.contours, color, working, indices);
184        let _: FillRule = rule;
185        mesh
186    }
187
188    /// Triangulates a set of contours and returns the resulting mesh.
189    ///
190    /// # Arguments
191    ///
192    /// - `&[Contour]` - Shared reference to a `[Contour]`.
193    /// - `u32` - A 32-bit unsigned integer (`u32`).
194    /// - `FillRule` - A `FillRule` parameter.
195    ///
196    /// # Returns
197    ///
198    /// - `SolidMesh` - A `SolidMesh` value.
199    pub fn fill_contours(&mut self, contours: &[Contour], color: u32, rule: FillRule) -> SolidMesh {
200        let mut mesh: SolidMesh = SolidMesh::new();
201        let Self {
202            working, indices, ..
203        } = self;
204        fill_contours_into(&mut mesh, contours, color, working, indices);
205        let _: FillRule = rule;
206        mesh
207    }
208
209    /// Builds a triangle strip for the region between a polyline and a
210    /// straight baseline.
211    ///
212    /// This is the hot path for area charts: the shape is a strip by
213    /// construction, so it needs no triangulation at all, and the whole
214    /// series becomes a single indexed draw call.
215    ///
216    /// `points` are the polyline vertices in order; `baseline` is the
217    /// vertical coordinate the area is closed against.
218    ///
219    /// # Arguments
220    ///
221    /// - `&[Point]` - Shared reference to a `[Point]`.
222    /// - `f32` - A 32-bit float (`f32`).
223    /// - `u32` - A 32-bit unsigned integer (`u32`).
224    ///
225    /// # Returns
226    ///
227    /// - `SolidMesh` - A `SolidMesh` value.
228    pub fn fill_strip(&mut self, points: &[Point], baseline: f32, color: u32) -> SolidMesh {
229        let mut mesh: SolidMesh = SolidMesh::new();
230        if points.len() < 2 {
231            return mesh;
232        }
233        mesh.reserve(points.len() * 4, points.len() * 2);
234        for pair in points.windows(2) {
235            let a: Point = pair[0];
236            let b: Point = pair[1];
237            let base_a: SolidVertex = SolidVertex {
238                x: a.x,
239                y: baseline,
240                color,
241            };
242            let base_b: SolidVertex = SolidVertex {
243                x: b.x,
244                y: baseline,
245                color,
246            };
247            let top_a: SolidVertex = SolidVertex {
248                x: a.x,
249                y: a.y,
250                color,
251            };
252            let top_b: SolidVertex = SolidVertex {
253                x: b.x,
254                y: b.y,
255                color,
256            };
257            mesh.push_quad(base_a, base_b, top_b, top_a);
258        }
259        mesh
260    }
261
262    /// Expands the path into stroke geometry and returns the mesh.
263    ///
264    /// Every segment becomes a quad, and every join becomes a wedge, so the
265    /// whole stroke is a single indexed draw call regardless of how many
266    /// segments it has.
267    ///
268    /// # Arguments
269    ///
270    /// - `&Path` - Shared reference to a `Path`.
271    /// - `&StrokeStyle` - Shared reference to a `StrokeStyle`.
272    /// - `u32` - A 32-bit unsigned integer (`u32`).
273    ///
274    /// # Returns
275    ///
276    /// - `SolidMesh` - A `SolidMesh` value.
277    pub fn stroke(&mut self, path: &Path, style: &StrokeStyle, color: u32) -> SolidMesh {
278        self.flatten(path);
279        let mut mesh: SolidMesh = SolidMesh::new();
280        stroke_contours_into(&mut mesh, &self.get_flat().contours, style, color);
281        mesh
282    }
283
284    /// Expands a set of contours into stroke geometry.
285    ///
286    /// # Arguments
287    ///
288    /// - `&[Contour]` - Shared reference to a `[Contour]`.
289    /// - `&StrokeStyle` - Shared reference to a `StrokeStyle`.
290    /// - `u32` - A 32-bit unsigned integer (`u32`).
291    ///
292    /// # Returns
293    ///
294    /// - `SolidMesh` - A `SolidMesh` value.
295    pub fn stroke_contours(
296        &mut self,
297        contours: &[Contour],
298        style: &StrokeStyle,
299        color: u32,
300    ) -> SolidMesh {
301        let mut mesh: SolidMesh = SolidMesh::new();
302        stroke_contours_into(&mut mesh, contours, style, color);
303        mesh
304    }
305}
306
307/// Default construction for [`Tessellator`].
308impl Default for Tessellator {
309    /// Constructs a default [`Tessellator`] value.
310    fn default() -> Self {
311        Self::new()
312    }
313}
314
315/// Inherent implementation of [`SolidMesh`].
316impl SolidMesh {
317    /// Returns an empty mesh.
318    pub fn new() -> Self {
319        Self::default()
320    }
321
322    /// Returns `true` when the mesh holds no triangles.
323    ///
324    /// # Returns
325    ///
326    /// - `bool` - A boolean.
327    pub fn is_empty(&self) -> bool {
328        self.get_indices().is_empty()
329    }
330
331    /// Returns the number of triangles in the mesh.
332    ///
333    /// # Returns
334    ///
335    /// - `usize` - A non-negative integer.
336    pub fn triangle_count(&self) -> usize {
337        self.get_indices().len() / 3
338    }
339
340    /// Reserves room for the given number of vertices and triangles.
341    ///
342    /// # Arguments
343    ///
344    /// - `usize` - A non-negative integer (`usize`).
345    pub fn reserve(&mut self, vertices: usize, triangles: usize) {
346        self.get_mut_vertices().reserve(vertices);
347        self.get_mut_indices().reserve(triangles * 3);
348    }
349
350    /// Appends a triangle given its three vertices.
351    ///
352    /// # Arguments
353    ///
354    /// - `SolidVertex` - A `SolidVertex` parameter.
355    pub fn push_triangle(&mut self, a: SolidVertex, b: SolidVertex, c: SolidVertex) {
356        let base: u32 = self.get_vertices().len() as u32;
357        self.get_mut_vertices().push(a);
358        self.get_mut_vertices().push(b);
359        self.get_mut_vertices().push(c);
360        self.get_mut_indices().push(base);
361        self.get_mut_indices().push(base + 1);
362        self.get_mut_indices().push(base + 2);
363    }
364
365    /// Appends a quad given its four corners, in order.
366    ///
367    /// # Arguments
368    ///
369    /// - `SolidVertex` - A `SolidVertex` parameter.
370    pub fn push_quad(&mut self, a: SolidVertex, b: SolidVertex, c: SolidVertex, d: SolidVertex) {
371        let base: u32 = self.get_vertices().len() as u32;
372        self.get_mut_vertices().push(a);
373        self.get_mut_vertices().push(b);
374        self.get_mut_vertices().push(c);
375        self.get_mut_vertices().push(d);
376        self.get_mut_indices().push(base);
377        self.get_mut_indices().push(base + 1);
378        self.get_mut_indices().push(base + 2);
379        self.get_mut_indices().push(base);
380        self.get_mut_indices().push(base + 2);
381        self.get_mut_indices().push(base + 3);
382    }
383}
384
385/// Inherent implementation of [`TextMesh`].
386impl TextMesh {
387    /// Returns an empty mesh.
388    pub fn new() -> Self {
389        Self::default()
390    }
391
392    /// Returns `true` when the mesh holds no triangles.
393    ///
394    /// # Returns
395    ///
396    /// - `bool` - A boolean.
397    pub fn is_empty(&self) -> bool {
398        self.get_indices().is_empty()
399    }
400
401    /// Reserves room for the given number of quads.
402    ///
403    /// # Arguments
404    ///
405    /// - `usize` - A non-negative integer (`usize`).
406    pub fn reserve(&mut self, quads: usize) {
407        self.get_mut_vertices().reserve(quads * 4);
408        self.get_mut_indices().reserve(quads * 6);
409    }
410
411    /// Appends a textured quad.
412    ///
413    /// The corners are given in order — top left, top right, bottom
414    /// right, bottom left — and so are the texture coordinates that go
415    /// with them.
416    ///
417    /// # Arguments
418    ///
419    /// - `[(f32, f32); 4]` - A `[(f32, f32); 4]` parameter.
420    /// - `u32` - A 32-bit unsigned integer (`u32`).
421    pub fn push_quad(&mut self, corners: [(f32, f32); 4], uvs: [(f32, f32); 4], color: u32) {
422        let base: u32 = self.get_vertices().len() as u32;
423        for index in 0..4 {
424            let (x, y) = corners[index];
425            let (u, v) = uvs[index];
426            self.get_mut_vertices()
427                .push(TextVertex { x, y, u, v, color });
428        }
429        self.get_mut_indices().push(base);
430        self.get_mut_indices().push(base + 1);
431        self.get_mut_indices().push(base + 2);
432        self.get_mut_indices().push(base);
433        self.get_mut_indices().push(base + 2);
434        self.get_mut_indices().push(base + 3);
435    }
436}