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}