1#![allow(clippy::identity_op)]
7
8use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2};
9
10use crate::{
11 color::ColorMode, emath, stroke::PathStroke, texture_atlas::PreparedDisc, CircleShape,
12 ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape, EllipseShape, Mesh,
13 PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke, StrokeKind, TextShape,
14 TextureId, Vertex, WHITE_UV,
15};
16
17#[allow(clippy::approx_constant)]
20mod precomputed_vertices {
21 use emath::{vec2, Vec2};
32
33 pub const CIRCLE_8: [Vec2; 9] = [
34 vec2(1.000000, 0.000000),
35 vec2(0.707107, 0.707107),
36 vec2(0.000000, 1.000000),
37 vec2(-0.707107, 0.707107),
38 vec2(-1.000000, 0.000000),
39 vec2(-0.707107, -0.707107),
40 vec2(0.000000, -1.000000),
41 vec2(0.707107, -0.707107),
42 vec2(1.000000, 0.000000),
43 ];
44
45 pub const CIRCLE_16: [Vec2; 17] = [
46 vec2(1.000000, 0.000000),
47 vec2(0.923880, 0.382683),
48 vec2(0.707107, 0.707107),
49 vec2(0.382683, 0.923880),
50 vec2(0.000000, 1.000000),
51 vec2(-0.382684, 0.923880),
52 vec2(-0.707107, 0.707107),
53 vec2(-0.923880, 0.382683),
54 vec2(-1.000000, 0.000000),
55 vec2(-0.923880, -0.382683),
56 vec2(-0.707107, -0.707107),
57 vec2(-0.382684, -0.923880),
58 vec2(0.000000, -1.000000),
59 vec2(0.382684, -0.923879),
60 vec2(0.707107, -0.707107),
61 vec2(0.923880, -0.382683),
62 vec2(1.000000, 0.000000),
63 ];
64
65 pub const CIRCLE_32: [Vec2; 33] = [
66 vec2(1.000000, 0.000000),
67 vec2(0.980785, 0.195090),
68 vec2(0.923880, 0.382683),
69 vec2(0.831470, 0.555570),
70 vec2(0.707107, 0.707107),
71 vec2(0.555570, 0.831470),
72 vec2(0.382683, 0.923880),
73 vec2(0.195090, 0.980785),
74 vec2(0.000000, 1.000000),
75 vec2(-0.195090, 0.980785),
76 vec2(-0.382683, 0.923880),
77 vec2(-0.555570, 0.831470),
78 vec2(-0.707107, 0.707107),
79 vec2(-0.831470, 0.555570),
80 vec2(-0.923880, 0.382683),
81 vec2(-0.980785, 0.195090),
82 vec2(-1.000000, 0.000000),
83 vec2(-0.980785, -0.195090),
84 vec2(-0.923880, -0.382683),
85 vec2(-0.831470, -0.555570),
86 vec2(-0.707107, -0.707107),
87 vec2(-0.555570, -0.831470),
88 vec2(-0.382683, -0.923880),
89 vec2(-0.195090, -0.980785),
90 vec2(-0.000000, -1.000000),
91 vec2(0.195090, -0.980785),
92 vec2(0.382683, -0.923880),
93 vec2(0.555570, -0.831470),
94 vec2(0.707107, -0.707107),
95 vec2(0.831470, -0.555570),
96 vec2(0.923880, -0.382683),
97 vec2(0.980785, -0.195090),
98 vec2(1.000000, -0.000000),
99 ];
100
101 pub const CIRCLE_64: [Vec2; 65] = [
102 vec2(1.000000, 0.000000),
103 vec2(0.995185, 0.098017),
104 vec2(0.980785, 0.195090),
105 vec2(0.956940, 0.290285),
106 vec2(0.923880, 0.382683),
107 vec2(0.881921, 0.471397),
108 vec2(0.831470, 0.555570),
109 vec2(0.773010, 0.634393),
110 vec2(0.707107, 0.707107),
111 vec2(0.634393, 0.773010),
112 vec2(0.555570, 0.831470),
113 vec2(0.471397, 0.881921),
114 vec2(0.382683, 0.923880),
115 vec2(0.290285, 0.956940),
116 vec2(0.195090, 0.980785),
117 vec2(0.098017, 0.995185),
118 vec2(0.000000, 1.000000),
119 vec2(-0.098017, 0.995185),
120 vec2(-0.195090, 0.980785),
121 vec2(-0.290285, 0.956940),
122 vec2(-0.382683, 0.923880),
123 vec2(-0.471397, 0.881921),
124 vec2(-0.555570, 0.831470),
125 vec2(-0.634393, 0.773010),
126 vec2(-0.707107, 0.707107),
127 vec2(-0.773010, 0.634393),
128 vec2(-0.831470, 0.555570),
129 vec2(-0.881921, 0.471397),
130 vec2(-0.923880, 0.382683),
131 vec2(-0.956940, 0.290285),
132 vec2(-0.980785, 0.195090),
133 vec2(-0.995185, 0.098017),
134 vec2(-1.000000, 0.000000),
135 vec2(-0.995185, -0.098017),
136 vec2(-0.980785, -0.195090),
137 vec2(-0.956940, -0.290285),
138 vec2(-0.923880, -0.382683),
139 vec2(-0.881921, -0.471397),
140 vec2(-0.831470, -0.555570),
141 vec2(-0.773010, -0.634393),
142 vec2(-0.707107, -0.707107),
143 vec2(-0.634393, -0.773010),
144 vec2(-0.555570, -0.831470),
145 vec2(-0.471397, -0.881921),
146 vec2(-0.382683, -0.923880),
147 vec2(-0.290285, -0.956940),
148 vec2(-0.195090, -0.980785),
149 vec2(-0.098017, -0.995185),
150 vec2(-0.000000, -1.000000),
151 vec2(0.098017, -0.995185),
152 vec2(0.195090, -0.980785),
153 vec2(0.290285, -0.956940),
154 vec2(0.382683, -0.923880),
155 vec2(0.471397, -0.881921),
156 vec2(0.555570, -0.831470),
157 vec2(0.634393, -0.773010),
158 vec2(0.707107, -0.707107),
159 vec2(0.773010, -0.634393),
160 vec2(0.831470, -0.555570),
161 vec2(0.881921, -0.471397),
162 vec2(0.923880, -0.382683),
163 vec2(0.956940, -0.290285),
164 vec2(0.980785, -0.195090),
165 vec2(0.995185, -0.098017),
166 vec2(1.000000, -0.000000),
167 ];
168
169 pub const CIRCLE_128: [Vec2; 129] = [
170 vec2(1.000000, 0.000000),
171 vec2(0.998795, 0.049068),
172 vec2(0.995185, 0.098017),
173 vec2(0.989177, 0.146730),
174 vec2(0.980785, 0.195090),
175 vec2(0.970031, 0.242980),
176 vec2(0.956940, 0.290285),
177 vec2(0.941544, 0.336890),
178 vec2(0.923880, 0.382683),
179 vec2(0.903989, 0.427555),
180 vec2(0.881921, 0.471397),
181 vec2(0.857729, 0.514103),
182 vec2(0.831470, 0.555570),
183 vec2(0.803208, 0.595699),
184 vec2(0.773010, 0.634393),
185 vec2(0.740951, 0.671559),
186 vec2(0.707107, 0.707107),
187 vec2(0.671559, 0.740951),
188 vec2(0.634393, 0.773010),
189 vec2(0.595699, 0.803208),
190 vec2(0.555570, 0.831470),
191 vec2(0.514103, 0.857729),
192 vec2(0.471397, 0.881921),
193 vec2(0.427555, 0.903989),
194 vec2(0.382683, 0.923880),
195 vec2(0.336890, 0.941544),
196 vec2(0.290285, 0.956940),
197 vec2(0.242980, 0.970031),
198 vec2(0.195090, 0.980785),
199 vec2(0.146730, 0.989177),
200 vec2(0.098017, 0.995185),
201 vec2(0.049068, 0.998795),
202 vec2(0.000000, 1.000000),
203 vec2(-0.049068, 0.998795),
204 vec2(-0.098017, 0.995185),
205 vec2(-0.146730, 0.989177),
206 vec2(-0.195090, 0.980785),
207 vec2(-0.242980, 0.970031),
208 vec2(-0.290285, 0.956940),
209 vec2(-0.336890, 0.941544),
210 vec2(-0.382683, 0.923880),
211 vec2(-0.427555, 0.903989),
212 vec2(-0.471397, 0.881921),
213 vec2(-0.514103, 0.857729),
214 vec2(-0.555570, 0.831470),
215 vec2(-0.595699, 0.803208),
216 vec2(-0.634393, 0.773010),
217 vec2(-0.671559, 0.740951),
218 vec2(-0.707107, 0.707107),
219 vec2(-0.740951, 0.671559),
220 vec2(-0.773010, 0.634393),
221 vec2(-0.803208, 0.595699),
222 vec2(-0.831470, 0.555570),
223 vec2(-0.857729, 0.514103),
224 vec2(-0.881921, 0.471397),
225 vec2(-0.903989, 0.427555),
226 vec2(-0.923880, 0.382683),
227 vec2(-0.941544, 0.336890),
228 vec2(-0.956940, 0.290285),
229 vec2(-0.970031, 0.242980),
230 vec2(-0.980785, 0.195090),
231 vec2(-0.989177, 0.146730),
232 vec2(-0.995185, 0.098017),
233 vec2(-0.998795, 0.049068),
234 vec2(-1.000000, 0.000000),
235 vec2(-0.998795, -0.049068),
236 vec2(-0.995185, -0.098017),
237 vec2(-0.989177, -0.146730),
238 vec2(-0.980785, -0.195090),
239 vec2(-0.970031, -0.242980),
240 vec2(-0.956940, -0.290285),
241 vec2(-0.941544, -0.336890),
242 vec2(-0.923880, -0.382683),
243 vec2(-0.903989, -0.427555),
244 vec2(-0.881921, -0.471397),
245 vec2(-0.857729, -0.514103),
246 vec2(-0.831470, -0.555570),
247 vec2(-0.803208, -0.595699),
248 vec2(-0.773010, -0.634393),
249 vec2(-0.740951, -0.671559),
250 vec2(-0.707107, -0.707107),
251 vec2(-0.671559, -0.740951),
252 vec2(-0.634393, -0.773010),
253 vec2(-0.595699, -0.803208),
254 vec2(-0.555570, -0.831470),
255 vec2(-0.514103, -0.857729),
256 vec2(-0.471397, -0.881921),
257 vec2(-0.427555, -0.903989),
258 vec2(-0.382683, -0.923880),
259 vec2(-0.336890, -0.941544),
260 vec2(-0.290285, -0.956940),
261 vec2(-0.242980, -0.970031),
262 vec2(-0.195090, -0.980785),
263 vec2(-0.146730, -0.989177),
264 vec2(-0.098017, -0.995185),
265 vec2(-0.049068, -0.998795),
266 vec2(-0.000000, -1.000000),
267 vec2(0.049068, -0.998795),
268 vec2(0.098017, -0.995185),
269 vec2(0.146730, -0.989177),
270 vec2(0.195090, -0.980785),
271 vec2(0.242980, -0.970031),
272 vec2(0.290285, -0.956940),
273 vec2(0.336890, -0.941544),
274 vec2(0.382683, -0.923880),
275 vec2(0.427555, -0.903989),
276 vec2(0.471397, -0.881921),
277 vec2(0.514103, -0.857729),
278 vec2(0.555570, -0.831470),
279 vec2(0.595699, -0.803208),
280 vec2(0.634393, -0.773010),
281 vec2(0.671559, -0.740951),
282 vec2(0.707107, -0.707107),
283 vec2(0.740951, -0.671559),
284 vec2(0.773010, -0.634393),
285 vec2(0.803208, -0.595699),
286 vec2(0.831470, -0.555570),
287 vec2(0.857729, -0.514103),
288 vec2(0.881921, -0.471397),
289 vec2(0.903989, -0.427555),
290 vec2(0.923880, -0.382683),
291 vec2(0.941544, -0.336890),
292 vec2(0.956940, -0.290285),
293 vec2(0.970031, -0.242980),
294 vec2(0.980785, -0.195090),
295 vec2(0.989177, -0.146730),
296 vec2(0.995185, -0.098017),
297 vec2(0.998795, -0.049068),
298 vec2(1.000000, -0.000000),
299 ];
300}
301
302#[derive(Clone, Copy, Debug, Default, PartialEq)]
305struct PathPoint {
306 pos: Pos2,
307
308 normal: Vec2,
318}
319
320#[derive(Clone, Debug, Default)]
324pub struct Path(Vec<PathPoint>);
325
326impl Path {
327 #[inline(always)]
328 pub fn clear(&mut self) {
329 self.0.clear();
330 }
331
332 #[inline(always)]
333 pub fn reserve(&mut self, additional: usize) {
334 self.0.reserve(additional);
335 }
336
337 #[inline(always)]
338 pub fn add_point(&mut self, pos: Pos2, normal: Vec2) {
339 self.0.push(PathPoint { pos, normal });
340 }
341
342 pub fn add_circle(&mut self, center: Pos2, radius: f32) {
343 use precomputed_vertices::{CIRCLE_128, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_8};
344
345 if radius <= 2.0 {
349 self.0.extend(CIRCLE_8.iter().map(|&n| PathPoint {
350 pos: center + radius * n,
351 normal: n,
352 }));
353 } else if radius <= 5.0 {
354 self.0.extend(CIRCLE_16.iter().map(|&n| PathPoint {
355 pos: center + radius * n,
356 normal: n,
357 }));
358 } else if radius < 18.0 {
359 self.0.extend(CIRCLE_32.iter().map(|&n| PathPoint {
360 pos: center + radius * n,
361 normal: n,
362 }));
363 } else if radius < 50.0 {
364 self.0.extend(CIRCLE_64.iter().map(|&n| PathPoint {
365 pos: center + radius * n,
366 normal: n,
367 }));
368 } else {
369 self.0.extend(CIRCLE_128.iter().map(|&n| PathPoint {
370 pos: center + radius * n,
371 normal: n,
372 }));
373 }
374 }
375
376 pub fn add_line_segment(&mut self, points: [Pos2; 2]) {
377 self.reserve(2);
378 let normal = (points[1] - points[0]).normalized().rot90();
379 self.add_point(points[0], normal);
380 self.add_point(points[1], normal);
381 }
382
383 pub fn add_open_points(&mut self, points: &[Pos2]) {
384 let n = points.len();
385 assert!(n >= 2);
386
387 if n == 2 {
388 self.add_line_segment([points[0], points[1]]);
390 } else {
391 self.reserve(n);
392 self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
393 let mut n0 = (points[1] - points[0]).normalized().rot90();
394 for i in 1..n - 1 {
395 let mut n1 = (points[i + 1] - points[i]).normalized().rot90();
396
397 if n0 == Vec2::ZERO {
399 n0 = n1;
400 } else if n1 == Vec2::ZERO {
401 n1 = n0;
402 }
403
404 let normal = (n0 + n1) / 2.0;
405 let length_sq = normal.length_sq();
406 let right_angle_length_sq = 0.5;
407 let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
408 if sharper_than_a_right_angle {
409 let center_normal = normal.normalized();
411 let n0c = (n0 + center_normal) / 2.0;
412 let n1c = (n1 + center_normal) / 2.0;
413 self.add_point(points[i], n0c / n0c.length_sq());
414 self.add_point(points[i], n1c / n1c.length_sq());
415 } else {
416 self.add_point(points[i], normal / length_sq);
418 }
419
420 n0 = n1;
421 }
422 self.add_point(
423 points[n - 1],
424 (points[n - 1] - points[n - 2]).normalized().rot90(),
425 );
426 }
427 }
428
429 pub fn add_line_loop(&mut self, points: &[Pos2]) {
430 let n = points.len();
431 assert!(n >= 2);
432 self.reserve(n);
433
434 let mut n0 = (points[0] - points[n - 1]).normalized().rot90();
435
436 for i in 0..n {
437 let next_i = if i + 1 == n { 0 } else { i + 1 };
438 let mut n1 = (points[next_i] - points[i]).normalized().rot90();
439
440 if n0 == Vec2::ZERO {
442 n0 = n1;
443 } else if n1 == Vec2::ZERO {
444 n1 = n0;
445 }
446
447 let normal = (n0 + n1) / 2.0;
448 let length_sq = normal.length_sq();
449
450 const CUT_OFF_SHARP_CORNERS: bool = false;
459
460 let right_angle_length_sq = 0.5;
461 let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
462 if CUT_OFF_SHARP_CORNERS && sharper_than_a_right_angle {
463 let center_normal = normal.normalized();
465 let n0c = (n0 + center_normal) / 2.0;
466 let n1c = (n1 + center_normal) / 2.0;
467 self.add_point(points[i], n0c / n0c.length_sq());
468 self.add_point(points[i], n1c / n1c.length_sq());
469 } else {
470 self.add_point(points[i], normal / length_sq);
472 }
473
474 n0 = n1;
475 }
476 }
477
478 pub fn fill_and_stroke(
483 &mut self,
484 feathering: f32,
485 fill: Color32,
486 stroke: &PathStroke,
487 out: &mut Mesh,
488 ) {
489 stroke_and_fill_path(feathering, &mut self.0, PathType::Closed, stroke, fill, out);
490 }
491
492 pub fn stroke_open(&mut self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) {
494 stroke_path(feathering, &mut self.0, PathType::Open, stroke, out);
495 }
496
497 pub fn stroke_closed(&mut self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) {
499 stroke_path(feathering, &mut self.0, PathType::Closed, stroke, out);
500 }
501
502 pub fn stroke(
503 &mut self,
504 feathering: f32,
505 path_type: PathType,
506 stroke: &PathStroke,
507 out: &mut Mesh,
508 ) {
509 stroke_path(feathering, &mut self.0, path_type, stroke, out);
510 }
511
512 pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
517 fill_closed_path(feathering, &mut self.0, color, out);
518 }
519
520 pub fn fill_with_uv(
524 &mut self,
525 feathering: f32,
526 color: Color32,
527 texture_id: TextureId,
528 uv_from_pos: impl Fn(Pos2) -> Pos2,
529 out: &mut Mesh,
530 ) {
531 fill_closed_path_with_uv(feathering, &mut self.0, color, texture_id, uv_from_pos, out);
532 }
533}
534
535pub mod path {
536 use crate::CornerRadiusF32;
538 use emath::{pos2, Pos2, Rect};
539
540 pub fn rounded_rectangle(path: &mut Vec<Pos2>, rect: Rect, cr: CornerRadiusF32) {
542 path.clear();
543
544 let min = rect.min;
545 let max = rect.max;
546
547 let cr = clamp_corner_radius(cr, rect);
548
549 if cr == CornerRadiusF32::ZERO {
550 path.reserve(4);
551 path.push(pos2(min.x, min.y)); path.push(pos2(max.x, min.y)); path.push(pos2(max.x, max.y)); path.push(pos2(min.x, max.y)); } else {
556 let eps = f32::EPSILON * rect.size().max_elem();
559
560 add_circle_quadrant(path, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0); if rect.width() <= cr.se + cr.sw + eps {
563 path.pop(); }
565
566 add_circle_quadrant(path, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0); if rect.height() <= cr.sw + cr.nw + eps {
569 path.pop(); }
571
572 add_circle_quadrant(path, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0); if rect.width() <= cr.nw + cr.ne + eps {
575 path.pop(); }
577
578 add_circle_quadrant(path, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0); if rect.height() <= cr.ne + cr.se + eps {
581 path.pop(); }
583 }
584 }
585
586 pub fn add_circle_quadrant(path: &mut Vec<Pos2>, center: Pos2, radius: f32, quadrant: f32) {
605 use super::precomputed_vertices::{CIRCLE_128, CIRCLE_16, CIRCLE_32, CIRCLE_64, CIRCLE_8};
606
607 if radius <= 0.0 {
611 path.push(center);
612 } else if radius <= 2.0 {
613 let offset = quadrant as usize * 2;
614 let quadrant_vertices = &CIRCLE_8[offset..=offset + 2];
615 path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
616 } else if radius <= 5.0 {
617 let offset = quadrant as usize * 4;
618 let quadrant_vertices = &CIRCLE_16[offset..=offset + 4];
619 path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
620 } else if radius < 18.0 {
621 let offset = quadrant as usize * 8;
622 let quadrant_vertices = &CIRCLE_32[offset..=offset + 8];
623 path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
624 } else if radius < 50.0 {
625 let offset = quadrant as usize * 16;
626 let quadrant_vertices = &CIRCLE_64[offset..=offset + 16];
627 path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
628 } else {
629 let offset = quadrant as usize * 32;
630 let quadrant_vertices = &CIRCLE_128[offset..=offset + 32];
631 path.extend(quadrant_vertices.iter().map(|&n| center + radius * n));
632 }
633 }
634
635 fn clamp_corner_radius(cr: CornerRadiusF32, rect: Rect) -> CornerRadiusF32 {
637 let half_width = rect.width() * 0.5;
638 let half_height = rect.height() * 0.5;
639 let max_cr = half_width.min(half_height);
640 cr.at_most(max_cr).at_least(0.0)
641 }
642}
643
644#[derive(Clone, Copy, PartialEq, Eq)]
647pub enum PathType {
648 Open,
649 Closed,
650}
651
652#[derive(Clone, Copy, Debug, PartialEq)]
654#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
655#[cfg_attr(feature = "serde", serde(default))]
656pub struct TessellationOptions {
657 pub feathering: bool,
668
669 pub feathering_size_in_pixels: f32,
674
675 pub coarse_tessellation_culling: bool,
678
679 pub prerasterized_discs: bool,
682
683 pub round_text_to_pixels: bool,
686
687 pub round_line_segments_to_pixels: bool,
691
692 pub round_rects_to_pixels: bool,
699
700 pub debug_paint_clip_rects: bool,
702
703 pub debug_paint_text_rects: bool,
705
706 pub debug_ignore_clip_rects: bool,
708
709 pub bezier_tolerance: f32,
711
712 pub epsilon: f32,
714
715 pub parallel_tessellation: bool,
717
718 pub validate_meshes: bool,
723}
724
725impl Default for TessellationOptions {
726 fn default() -> Self {
727 Self {
728 feathering: true,
729 feathering_size_in_pixels: 1.0,
730 coarse_tessellation_culling: true,
731 prerasterized_discs: true,
732 round_text_to_pixels: true,
733 round_line_segments_to_pixels: true,
734 round_rects_to_pixels: true,
735 debug_paint_text_rects: false,
736 debug_paint_clip_rects: false,
737 debug_ignore_clip_rects: false,
738 bezier_tolerance: 0.1,
739 epsilon: 1.0e-5,
740 parallel_tessellation: true,
741 validate_meshes: false,
742 }
743 }
744}
745
746fn cw_signed_area(path: &[PathPoint]) -> f64 {
747 if let Some(last) = path.last() {
748 let mut previous = last.pos;
749 let mut area = 0.0;
750 for p in path {
751 area += (previous.x * p.pos.y - p.pos.x * previous.y) as f64;
752 previous = p.pos;
753 }
754 area
755 } else {
756 0.0
757 }
758}
759
760fn fill_closed_path(feathering: f32, path: &mut [PathPoint], fill_color: Color32, out: &mut Mesh) {
766 if fill_color == Color32::TRANSPARENT {
767 return;
768 }
769
770 let n = path.len() as u32;
771 if n < 3 {
772 return;
773 }
774
775 if 0.0 < feathering {
776 if cw_signed_area(path) < 0.0 {
777 path.reverse();
779 for point in &mut *path {
780 point.normal = -point.normal;
781 }
782 }
783
784 out.reserve_triangles(3 * n as usize);
785 out.reserve_vertices(2 * n as usize);
786 let idx_inner = out.vertices.len() as u32;
787 let idx_outer = idx_inner + 1;
788
789 for i in 2..n {
791 out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
792 }
793
794 let mut i0 = n - 1;
796 for i1 in 0..n {
797 let p1 = &path[i1 as usize];
798 let dm = 0.5 * feathering * p1.normal;
799
800 let pos_inner = p1.pos - dm;
801 let pos_outer = p1.pos + dm;
802
803 out.colored_vertex(pos_inner, fill_color);
804 out.colored_vertex(pos_outer, Color32::TRANSPARENT);
805 out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
806 out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
807 i0 = i1;
808 }
809 } else {
810 out.reserve_triangles(n as usize);
811 let idx = out.vertices.len() as u32;
812 out.vertices.extend(path.iter().map(|p| Vertex {
813 pos: p.pos,
814 uv: WHITE_UV,
815 color: fill_color,
816 }));
817 for i in 2..n {
818 out.add_triangle(idx, idx + i - 1, idx + i);
819 }
820 }
821}
822
823fn fill_closed_path_with_uv(
827 feathering: f32,
828 path: &mut [PathPoint],
829 color: Color32,
830 texture_id: TextureId,
831 uv_from_pos: impl Fn(Pos2) -> Pos2,
832 out: &mut Mesh,
833) {
834 if color == Color32::TRANSPARENT {
835 return;
836 }
837
838 if out.is_empty() {
839 out.texture_id = texture_id;
840 } else {
841 assert_eq!(
842 out.texture_id, texture_id,
843 "Mixing different `texture_id` in the same "
844 );
845 }
846
847 let n = path.len() as u32;
848 if 0.0 < feathering {
849 if cw_signed_area(path) < 0.0 {
850 path.reverse();
852 for point in &mut *path {
853 point.normal = -point.normal;
854 }
855 }
856
857 out.reserve_triangles(3 * n as usize);
858 out.reserve_vertices(2 * n as usize);
859 let color_outer = Color32::TRANSPARENT;
860 let idx_inner = out.vertices.len() as u32;
861 let idx_outer = idx_inner + 1;
862
863 for i in 2..n {
865 out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
866 }
867
868 let mut i0 = n - 1;
870 for i1 in 0..n {
871 let p1 = &path[i1 as usize];
872 let dm = 0.5 * feathering * p1.normal;
873
874 let pos = p1.pos - dm;
875 out.vertices.push(Vertex {
876 pos,
877 uv: uv_from_pos(pos),
878 color,
879 });
880
881 let pos = p1.pos + dm;
882 out.vertices.push(Vertex {
883 pos,
884 uv: uv_from_pos(pos),
885 color: color_outer,
886 });
887
888 out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
889 out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
890 i0 = i1;
891 }
892 } else {
893 out.reserve_triangles(n as usize);
894 let idx = out.vertices.len() as u32;
895 out.vertices.extend(path.iter().map(|p| Vertex {
896 pos: p.pos,
897 uv: uv_from_pos(p.pos),
898 color,
899 }));
900 for i in 2..n {
901 out.add_triangle(idx, idx + i - 1, idx + i);
902 }
903 }
904}
905
906fn stroke_path(
908 feathering: f32,
909 path: &mut [PathPoint],
910 path_type: PathType,
911 stroke: &PathStroke,
912 out: &mut Mesh,
913) {
914 let fill = Color32::TRANSPARENT;
915 stroke_and_fill_path(feathering, path, path_type, stroke, fill, out);
916}
917
918fn stroke_and_fill_path(
924 feathering: f32,
925 path: &mut [PathPoint],
926 path_type: PathType,
927 stroke: &PathStroke,
928 color_fill: Color32,
929 out: &mut Mesh,
930) {
931 let n = path.len() as u32;
932
933 if n < 2 {
934 return;
935 }
936
937 if stroke.width == 0.0 {
938 return fill_closed_path(feathering, path, color_fill, out);
940 }
941
942 if color_fill != Color32::TRANSPARENT && cw_signed_area(path) < 0.0 {
943 path.reverse();
945 for point in &mut *path {
946 point.normal = -point.normal;
947 }
948 }
949
950 if stroke.color == ColorMode::TRANSPARENT {
951 match stroke.kind {
953 StrokeKind::Inside => {
954 for point in &mut *path {
955 point.pos -= stroke.width * point.normal;
956 }
957 }
958 StrokeKind::Middle => {
959 for point in &mut *path {
960 point.pos -= 0.5 * stroke.width * point.normal;
961 }
962 }
963 StrokeKind::Outside => {}
964 }
965
966 return fill_closed_path(feathering, path, color_fill, out);
968 }
969
970 let idx = out.vertices.len() as u32;
971
972 match stroke.kind {
974 StrokeKind::Inside => {
975 for point in &mut *path {
976 point.pos -= 0.5 * stroke.width * point.normal;
977 }
978 }
979 StrokeKind::Middle => {
980 }
982 StrokeKind::Outside => {
983 for point in &mut *path {
984 point.pos += 0.5 * stroke.width * point.normal;
985 }
986 }
987 }
988
989 let uv_bbox = if matches!(stroke.color, ColorMode::UV(_)) {
991 Rect::from_points(&path.iter().map(|p| p.pos).collect::<Vec<Pos2>>())
992 .expand((stroke.width / 2.0) + feathering)
993 } else {
994 Rect::NAN
995 };
996 let get_color = |col: &ColorMode, pos: Pos2| match col {
997 ColorMode::Solid(col) => *col,
998 ColorMode::UV(fun) => fun(uv_bbox, pos),
999 };
1000
1001 if 0.0 < feathering {
1002 let color_outer = Color32::TRANSPARENT;
1003 let color_middle = &stroke.color;
1004
1005 let thin_line = stroke.width <= 0.9 * feathering;
1010 if thin_line {
1011 if color_fill != Color32::TRANSPARENT && stroke.width < feathering {
1016 for point in &mut *path {
1020 point.pos += 0.5 * (feathering - stroke.width) * point.normal;
1021 }
1022 }
1023
1024 let opacity = stroke.width / feathering;
1027
1028 out.reserve_triangles(4 * n as usize);
1036 out.reserve_vertices(3 * n as usize);
1037
1038 let mut i0 = n - 1;
1039 for i1 in 0..n {
1040 let connect_with_previous = path_type == PathType::Closed || i1 > 0;
1041 let p1 = path[i1 as usize];
1042 let p = p1.pos;
1043 let n = p1.normal;
1044 out.colored_vertex(p + n * feathering, color_outer);
1045 out.colored_vertex(p, mul_color(get_color(color_middle, p), opacity));
1046 out.colored_vertex(p - n * feathering, color_fill);
1047
1048 if connect_with_previous {
1049 out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
1050 out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
1051
1052 out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
1053 out.add_triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
1054 }
1055
1056 i0 = i1;
1057 }
1058
1059 if color_fill != Color32::TRANSPARENT {
1060 out.reserve_triangles(n as usize - 2);
1061 let idx_fill = idx + 2;
1062 for i in 2..n {
1063 out.add_triangle(idx_fill + 3 * (i - 1), idx_fill, idx_fill + 3 * i);
1064 }
1065 }
1066 } else {
1067 let inner_rad = 0.5 * (stroke.width - feathering);
1080 let outer_rad = 0.5 * (stroke.width + feathering);
1081
1082 match path_type {
1083 PathType::Closed => {
1084 out.reserve_triangles(6 * n as usize);
1085 out.reserve_vertices(4 * n as usize);
1086
1087 let mut i0 = n - 1;
1088 for i1 in 0..n {
1089 let p1 = path[i1 as usize];
1090 let p = p1.pos;
1091 let n = p1.normal;
1092 out.colored_vertex(p + n * outer_rad, color_outer);
1093 out.colored_vertex(
1094 p + n * inner_rad,
1095 get_color(color_middle, p + n * inner_rad),
1096 );
1097 out.colored_vertex(
1098 p - n * inner_rad,
1099 get_color(color_middle, p - n * inner_rad),
1100 );
1101 out.colored_vertex(p - n * outer_rad, color_fill);
1102
1103 out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
1104 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
1105
1106 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
1107 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
1108
1109 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
1110 out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
1111
1112 i0 = i1;
1113 }
1114
1115 if color_fill != Color32::TRANSPARENT {
1116 out.reserve_triangles(n as usize - 2);
1117 let idx_fill = idx + 3;
1118 for i in 2..n {
1119 out.add_triangle(idx_fill + 4 * (i - 1), idx_fill, idx_fill + 4 * i);
1120 }
1121 }
1122 }
1123 PathType::Open => {
1124 out.reserve_triangles(6 * n as usize + 4);
1143 out.reserve_vertices(4 * n as usize);
1144
1145 {
1146 let end = path[0];
1147 let p = end.pos;
1148 let n = end.normal;
1149 let back_extrude = n.rot90() * feathering;
1150 out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
1151 out.colored_vertex(
1152 p + n * inner_rad,
1153 get_color(color_middle, p + n * inner_rad),
1154 );
1155 out.colored_vertex(
1156 p - n * inner_rad,
1157 get_color(color_middle, p - n * inner_rad),
1158 );
1159 out.colored_vertex(p - n * outer_rad + back_extrude, color_outer);
1160
1161 out.add_triangle(idx + 0, idx + 1, idx + 2);
1162 out.add_triangle(idx + 0, idx + 2, idx + 3);
1163 }
1164
1165 let mut i0 = 0;
1166 for i1 in 1..n - 1 {
1167 let point = path[i1 as usize];
1168 let p = point.pos;
1169 let n = point.normal;
1170 out.colored_vertex(p + n * outer_rad, color_outer);
1171 out.colored_vertex(
1172 p + n * inner_rad,
1173 get_color(color_middle, p + n * inner_rad),
1174 );
1175 out.colored_vertex(
1176 p - n * inner_rad,
1177 get_color(color_middle, p - n * inner_rad),
1178 );
1179 out.colored_vertex(p - n * outer_rad, color_outer);
1180
1181 out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
1182 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
1183
1184 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
1185 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
1186
1187 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
1188 out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
1189
1190 i0 = i1;
1191 }
1192
1193 {
1194 let i1 = n - 1;
1195 let end = path[i1 as usize];
1196 let p = end.pos;
1197 let n = end.normal;
1198 let back_extrude = -n.rot90() * feathering;
1199 out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
1200 out.colored_vertex(
1201 p + n * inner_rad,
1202 get_color(color_middle, p + n * inner_rad),
1203 );
1204 out.colored_vertex(
1205 p - n * inner_rad,
1206 get_color(color_middle, p - n * inner_rad),
1207 );
1208 out.colored_vertex(p - n * outer_rad + back_extrude, color_outer);
1209
1210 out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
1211 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
1212
1213 out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
1214 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
1215
1216 out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
1217 out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
1218
1219 out.add_triangle(idx + 4 * i1 + 0, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
1221 out.add_triangle(idx + 4 * i1 + 0, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
1222 }
1223 }
1224 }
1225 }
1226 } else {
1227 out.reserve_triangles(2 * n as usize);
1229 out.reserve_vertices(2 * n as usize);
1230
1231 let last_index = if path_type == PathType::Closed {
1232 n
1233 } else {
1234 n - 1
1235 };
1236 for i in 0..last_index {
1237 out.add_triangle(
1238 idx + (2 * i + 0) % (2 * n),
1239 idx + (2 * i + 1) % (2 * n),
1240 idx + (2 * i + 2) % (2 * n),
1241 );
1242 out.add_triangle(
1243 idx + (2 * i + 2) % (2 * n),
1244 idx + (2 * i + 1) % (2 * n),
1245 idx + (2 * i + 3) % (2 * n),
1246 );
1247 }
1248
1249 let thin_line = stroke.width <= feathering;
1250 if thin_line {
1251 let opacity = stroke.width / feathering;
1253 let radius = feathering / 2.0;
1254 for p in path.iter_mut() {
1255 out.colored_vertex(
1256 p.pos + radius * p.normal,
1257 mul_color(get_color(&stroke.color, p.pos + radius * p.normal), opacity),
1258 );
1259 out.colored_vertex(
1260 p.pos - radius * p.normal,
1261 mul_color(get_color(&stroke.color, p.pos - radius * p.normal), opacity),
1262 );
1263 }
1264 } else {
1265 let radius = stroke.width / 2.0;
1266 for p in path.iter_mut() {
1267 out.colored_vertex(
1268 p.pos + radius * p.normal,
1269 get_color(&stroke.color, p.pos + radius * p.normal),
1270 );
1271 out.colored_vertex(
1272 p.pos - radius * p.normal,
1273 get_color(&stroke.color, p.pos - radius * p.normal),
1274 );
1275 }
1276 }
1277
1278 if color_fill != Color32::TRANSPARENT {
1279 for point in &mut *path {
1284 point.pos -= 0.5 * stroke.width * point.normal;
1285 }
1286 fill_closed_path(feathering, path, color_fill, out);
1288 }
1289 }
1290}
1291
1292fn mul_color(color: Color32, factor: f32) -> Color32 {
1293 color.gamma_multiply(factor)
1296}
1297
1298#[derive(Clone)]
1306pub struct Tessellator {
1307 pixels_per_point: f32,
1308 options: TessellationOptions,
1309 font_tex_size: [usize; 2],
1310
1311 prepared_discs: Vec<PreparedDisc>,
1313
1314 feathering: f32,
1316
1317 clip_rect: Rect,
1319
1320 scratchpad_points: Vec<Pos2>,
1321 scratchpad_path: Path,
1322}
1323
1324impl Tessellator {
1325 pub fn new(
1333 pixels_per_point: f32,
1334 options: TessellationOptions,
1335 font_tex_size: [usize; 2],
1336 prepared_discs: Vec<PreparedDisc>,
1337 ) -> Self {
1338 let feathering = if options.feathering {
1339 let pixel_size = 1.0 / pixels_per_point;
1340 options.feathering_size_in_pixels * pixel_size
1341 } else {
1342 0.0
1343 };
1344 Self {
1345 pixels_per_point,
1346 options,
1347 font_tex_size,
1348 prepared_discs,
1349 feathering,
1350 clip_rect: Rect::EVERYTHING,
1351 scratchpad_points: Default::default(),
1352 scratchpad_path: Default::default(),
1353 }
1354 }
1355
1356 pub fn set_clip_rect(&mut self, clip_rect: Rect) {
1358 self.clip_rect = clip_rect;
1359 }
1360
1361 pub fn tessellate_clipped_shape(
1363 &mut self,
1364 clipped_shape: ClippedShape,
1365 out_primitives: &mut Vec<ClippedPrimitive>,
1366 ) {
1367 let ClippedShape { clip_rect, shape } = clipped_shape;
1368
1369 if !clip_rect.is_positive() {
1370 return; }
1372
1373 if let Shape::Vec(shapes) = shape {
1374 for shape in shapes {
1375 self.tessellate_clipped_shape(ClippedShape { clip_rect, shape }, out_primitives);
1376 }
1377 return;
1378 }
1379
1380 if let Shape::Callback(callback) = shape {
1381 out_primitives.push(ClippedPrimitive {
1382 clip_rect,
1383 primitive: Primitive::Callback(callback),
1384 });
1385 return;
1386 }
1387
1388 let start_new_mesh = match out_primitives.last() {
1389 None => true,
1390 Some(output_clipped_primitive) => {
1391 output_clipped_primitive.clip_rect != clip_rect
1392 || match &output_clipped_primitive.primitive {
1393 Primitive::Mesh(output_mesh) => {
1394 output_mesh.texture_id != shape.texture_id()
1395 }
1396 Primitive::Callback(_) => true,
1397 }
1398 }
1399 };
1400
1401 if start_new_mesh {
1402 out_primitives.push(ClippedPrimitive {
1403 clip_rect,
1404 primitive: Primitive::Mesh(Mesh::default()),
1405 });
1406 }
1407
1408 let out = out_primitives.last_mut().unwrap();
1409
1410 if let Primitive::Mesh(out_mesh) = &mut out.primitive {
1411 self.clip_rect = clip_rect;
1412 self.tessellate_shape(shape, out_mesh);
1413 } else {
1414 unreachable!();
1415 }
1416 }
1417
1418 pub fn tessellate_shape(&mut self, shape: Shape, out: &mut Mesh) {
1425 match shape {
1426 Shape::Noop => {}
1427 Shape::Vec(vec) => {
1428 for shape in vec {
1429 self.tessellate_shape(shape, out);
1430 }
1431 }
1432 Shape::Circle(circle) => {
1433 self.tessellate_circle(circle, out);
1434 }
1435 Shape::Ellipse(ellipse) => {
1436 self.tessellate_ellipse(ellipse, out);
1437 }
1438 Shape::Mesh(mesh) => {
1439 profiling::scope!("mesh");
1440
1441 if self.options.validate_meshes && !mesh.is_valid() {
1442 debug_assert!(false, "Invalid Mesh in Shape::Mesh");
1443 return;
1444 }
1445 if self.options.coarse_tessellation_culling
1448 && !self.clip_rect.intersects(mesh.calc_bounds())
1449 {
1450 return;
1451 }
1452
1453 out.append_ref(&mesh);
1454 }
1455 Shape::LineSegment { points, stroke } => {
1456 self.tessellate_line_segment(points, stroke, out);
1457 }
1458 Shape::Path(path_shape) => {
1459 self.tessellate_path(&path_shape, out);
1460 }
1461 Shape::Rect(rect_shape) => {
1462 self.tessellate_rect(&rect_shape, out);
1463 }
1464 Shape::Text(text_shape) => {
1465 if self.options.debug_paint_text_rects {
1466 let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
1467 self.tessellate_rect(
1468 &RectShape::stroke(rect, 2.0, (0.5, Color32::GREEN), StrokeKind::Outside),
1469 out,
1470 );
1471 }
1472 self.tessellate_text(&text_shape, out);
1473 }
1474 Shape::QuadraticBezier(quadratic_shape) => {
1475 self.tessellate_quadratic_bezier(&quadratic_shape, out);
1476 }
1477 Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(&cubic_shape, out),
1478 Shape::Callback(_) => {
1479 panic!("Shape::Callback passed to Tessellator");
1480 }
1481 }
1482 }
1483
1484 pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) {
1489 let CircleShape {
1490 center,
1491 radius,
1492 mut fill,
1493 stroke,
1494 } = shape;
1495
1496 if radius <= 0.0 {
1497 return;
1498 }
1499
1500 if self.options.coarse_tessellation_culling
1501 && !self
1502 .clip_rect
1503 .expand(radius + stroke.width)
1504 .contains(center)
1505 {
1506 return;
1507 }
1508
1509 if self.options.prerasterized_discs && fill != Color32::TRANSPARENT {
1510 let radius_px = radius * self.pixels_per_point;
1511 let cutoff_radius = radius_px * 2.0_f32.powf(0.25);
1513
1514 for disc in &self.prepared_discs {
1517 if cutoff_radius <= disc.r {
1518 let side = radius_px * disc.w / (self.pixels_per_point * disc.r);
1519 let rect = Rect::from_center_size(center, Vec2::splat(side));
1520 out.add_rect_with_uv(rect, disc.uv, fill);
1521
1522 if stroke.is_empty() {
1523 return; } else {
1525 fill = Color32::TRANSPARENT; break;
1528 }
1529 }
1530 }
1531 }
1532
1533 let path_stroke = PathStroke::from(stroke).outside();
1534 self.scratchpad_path.clear();
1535 self.scratchpad_path.add_circle(center, radius);
1536 self.scratchpad_path
1537 .fill_and_stroke(self.feathering, fill, &path_stroke, out);
1538 }
1539
1540 pub fn tessellate_ellipse(&mut self, shape: EllipseShape, out: &mut Mesh) {
1545 let EllipseShape {
1546 center,
1547 radius,
1548 fill,
1549 stroke,
1550 } = shape;
1551
1552 if radius.x <= 0.0 || radius.y <= 0.0 {
1553 return;
1554 }
1555
1556 if self.options.coarse_tessellation_culling
1557 && !self
1558 .clip_rect
1559 .expand2(radius + Vec2::splat(stroke.width))
1560 .contains(center)
1561 {
1562 return;
1563 }
1564
1565 let max_radius = (radius.max_elem() * self.pixels_per_point) as u32;
1567
1568 let num_points = u32::max(8, max_radius / 16);
1570
1571 let ratio = ((radius.y / radius.x) / 2.0).clamp(0.0, 1.0);
1573
1574 let quarter: Vec<Vec2> = (1..num_points)
1576 .map(|i| {
1577 let percent = i as f32 / num_points as f32;
1578
1579 let eased = 2.0 * (percent - percent.powf(2.0)) * ratio + percent.powf(2.0);
1581
1582 let t = eased * std::f32::consts::FRAC_PI_2;
1584 Vec2::new(radius.x * f32::cos(t), radius.y * f32::sin(t))
1585 })
1586 .collect();
1587
1588 let mut points = Vec::new();
1591 points.push(center + Vec2::new(radius.x, 0.0));
1592 points.extend(quarter.iter().map(|p| center + *p));
1593 points.push(center + Vec2::new(0.0, radius.y));
1594 points.extend(quarter.iter().rev().map(|p| center + Vec2::new(-p.x, p.y)));
1595 points.push(center + Vec2::new(-radius.x, 0.0));
1596 points.extend(quarter.iter().map(|p| center - *p));
1597 points.push(center + Vec2::new(0.0, -radius.y));
1598 points.extend(quarter.iter().rev().map(|p| center + Vec2::new(p.x, -p.y)));
1599
1600 let path_stroke = PathStroke::from(stroke).outside();
1601 self.scratchpad_path.clear();
1602 self.scratchpad_path.add_line_loop(&points);
1603 self.scratchpad_path
1604 .fill_and_stroke(self.feathering, fill, &path_stroke, out);
1605 }
1606
1607 pub fn tessellate_mesh(&self, mesh: &Mesh, out: &mut Mesh) {
1612 if !mesh.is_valid() {
1613 debug_assert!(false, "Invalid Mesh in Shape::Mesh");
1614 return;
1615 }
1616
1617 if self.options.coarse_tessellation_culling
1618 && !self.clip_rect.intersects(mesh.calc_bounds())
1619 {
1620 return;
1621 }
1622
1623 out.append_ref(mesh);
1624 }
1625
1626 pub fn tessellate_line_segment(
1631 &mut self,
1632 mut points: [Pos2; 2],
1633 stroke: impl Into<Stroke>,
1634 out: &mut Mesh,
1635 ) {
1636 let stroke = stroke.into();
1637 if stroke.is_empty() {
1638 return;
1639 }
1640
1641 if self.options.coarse_tessellation_culling
1642 && !self
1643 .clip_rect
1644 .intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
1645 {
1646 return;
1647 }
1648
1649 if self.options.round_line_segments_to_pixels {
1650 let feathering = self.feathering;
1651 let pixels_per_point = self.pixels_per_point;
1652
1653 let quarter_pixel = 0.25 * feathering; let [a, b] = &mut points;
1656 if a.x == b.x {
1657 let mut x = a.x;
1659 round_line_segment(&mut x, &stroke, self.pixels_per_point);
1660 a.x = x;
1661 b.x = x;
1662
1663 if a.y < b.y {
1670 a.y = (a.y + quarter_pixel).round_to_pixel_center(pixels_per_point);
1671 b.y = (b.y - quarter_pixel).round_to_pixel_center(pixels_per_point);
1672 } else {
1673 a.y = (a.y - quarter_pixel).round_to_pixel_center(pixels_per_point);
1674 b.y = (b.y + quarter_pixel).round_to_pixel_center(pixels_per_point);
1675 }
1676 }
1677 if a.y == b.y {
1678 let mut y = a.y;
1680 round_line_segment(&mut y, &stroke, self.pixels_per_point);
1681 a.y = y;
1682 b.y = y;
1683
1684 if a.x < b.x {
1686 a.x = (a.x + quarter_pixel).round_to_pixel_center(pixels_per_point);
1687 b.x = (b.x - quarter_pixel).round_to_pixel_center(pixels_per_point);
1688 } else {
1689 a.x = (a.x - quarter_pixel).round_to_pixel_center(pixels_per_point);
1690 b.x = (b.x + quarter_pixel).round_to_pixel_center(pixels_per_point);
1691 }
1692 }
1693 }
1694
1695 self.scratchpad_path.clear();
1696 self.scratchpad_path.add_line_segment(points);
1697 self.scratchpad_path
1698 .stroke_open(self.feathering, &stroke.into(), out);
1699 }
1700
1701 #[deprecated = "Use `tessellate_line_segment` instead"]
1702 pub fn tessellate_line(
1703 &mut self,
1704 points: [Pos2; 2],
1705 stroke: impl Into<Stroke>,
1706 out: &mut Mesh,
1707 ) {
1708 self.tessellate_line_segment(points, stroke, out);
1709 }
1710
1711 pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) {
1716 if path_shape.points.len() < 2 {
1717 return;
1718 }
1719
1720 if self.options.coarse_tessellation_culling
1721 && !path_shape.visual_bounding_rect().intersects(self.clip_rect)
1722 {
1723 return;
1724 }
1725
1726 profiling::function_scope!();
1727
1728 let PathShape {
1729 points,
1730 closed,
1731 fill,
1732 stroke,
1733 } = path_shape;
1734
1735 self.scratchpad_path.clear();
1736
1737 if *closed {
1738 self.scratchpad_path.add_line_loop(points);
1739
1740 self.scratchpad_path
1741 .fill_and_stroke(self.feathering, *fill, stroke, out);
1742 } else {
1743 debug_assert_eq!(
1744 *fill,
1745 Color32::TRANSPARENT,
1746 "You asked to fill a path that is not closed. That makes no sense."
1747 );
1748
1749 self.scratchpad_path.add_open_points(points);
1750
1751 self.scratchpad_path
1752 .stroke(self.feathering, PathType::Open, stroke, out);
1753 }
1754 }
1755
1756 pub fn tessellate_rect(&mut self, rect_shape: &RectShape, out: &mut Mesh) {
1761 if self.options.coarse_tessellation_culling
1762 && !rect_shape.visual_bounding_rect().intersects(self.clip_rect)
1763 {
1764 return;
1765 }
1766
1767 let brush = rect_shape.brush.as_ref();
1768 let RectShape {
1769 mut rect,
1770 corner_radius,
1771 mut fill,
1772 mut stroke,
1773 mut stroke_kind,
1774 round_to_pixels,
1775 mut blur_width,
1776 brush: _, } = *rect_shape;
1778
1779 let mut corner_radius = CornerRadiusF32::from(corner_radius);
1780 let round_to_pixels = round_to_pixels.unwrap_or(self.options.round_rects_to_pixels);
1781 let pixel_size = 1.0 / self.pixels_per_point;
1782
1783 if stroke.width == 0.0 {
1784 stroke.color = Color32::TRANSPARENT;
1785 }
1786
1787 rect.min = rect.min.at_least(pos2(-1e7, -1e7));
1790 rect.max = rect.max.at_most(pos2(1e7, 1e7));
1791
1792 if !stroke.is_empty() {
1793 let rect_with_stroke = match stroke_kind {
1795 StrokeKind::Inside => rect,
1796 StrokeKind::Middle => rect.expand(stroke.width / 2.0),
1797 StrokeKind::Outside => rect.expand(stroke.width),
1798 };
1799
1800 if rect_with_stroke.size().min_elem() <= 2.0 * stroke.width + 0.5 * self.feathering {
1801 rect = rect_with_stroke;
1804
1805 fill = stroke.color;
1808
1809 stroke = Stroke::NONE;
1810 }
1811 }
1812
1813 if stroke.is_empty() {
1814 if rect.width() <= 2.0 * self.feathering {
1817 return self.tessellate_line_segment(
1818 [rect.center_top(), rect.center_bottom()],
1819 (rect.width(), fill),
1820 out,
1821 );
1822 }
1823 if rect.height() <= 2.0 * self.feathering {
1824 return self.tessellate_line_segment(
1825 [rect.left_center(), rect.right_center()],
1826 (rect.height(), fill),
1827 out,
1828 );
1829 }
1830 }
1831
1832 if round_to_pixels {
1834 match stroke_kind {
1837 StrokeKind::Inside => {
1838 rect = rect.round_to_pixels(self.pixels_per_point);
1849 }
1850 StrokeKind::Middle => {
1851 if stroke.width <= 0.0 {
1855 rect = rect.round_to_pixels(self.pixels_per_point);
1856 } else if stroke.width <= pixel_size
1857 || is_nearest_integer_odd(self.pixels_per_point * stroke.width)
1858 {
1859 rect = rect.round_to_pixel_center(self.pixels_per_point);
1860 } else {
1861 rect = rect.round_to_pixels(self.pixels_per_point);
1862 }
1863 }
1864 StrokeKind::Outside => {
1865 rect = rect.round_to_pixels(self.pixels_per_point);
1873 }
1874 }
1875 }
1876
1877 let old_feathering = self.feathering;
1878
1879 if self.feathering < blur_width {
1880 let eps = 0.1; blur_width = blur_width
1886 .at_most(rect.size().min_elem() - eps - 2.0 * stroke.width)
1887 .at_least(0.0);
1888
1889 corner_radius += 0.5 * blur_width;
1890
1891 self.feathering = self.feathering.max(blur_width);
1892 }
1893
1894 {
1895 let original_cr = corner_radius;
1900
1901 match stroke_kind {
1902 StrokeKind::Inside => {}
1903 StrokeKind::Middle => {
1904 rect = rect.expand(stroke.width / 2.0);
1905 corner_radius += stroke.width / 2.0;
1906 }
1907 StrokeKind::Outside => {
1908 rect = rect.expand(stroke.width);
1909 corner_radius += stroke.width;
1910 }
1911 }
1912
1913 stroke_kind = StrokeKind::Inside;
1914
1915 let min_inside_cr = 0.1; let min_outside_cr = stroke.width + min_inside_cr;
1923
1924 let extra_cr_tweak = 0.4; if original_cr.nw == 0.0 {
1927 corner_radius.nw = 0.0;
1928 } else {
1929 corner_radius.nw += extra_cr_tweak;
1930 corner_radius.nw = corner_radius.nw.at_least(min_outside_cr);
1931 }
1932 if original_cr.ne == 0.0 {
1933 corner_radius.ne = 0.0;
1934 } else {
1935 corner_radius.ne += extra_cr_tweak;
1936 corner_radius.ne = corner_radius.ne.at_least(min_outside_cr);
1937 }
1938 if original_cr.sw == 0.0 {
1939 corner_radius.sw = 0.0;
1940 } else {
1941 corner_radius.sw += extra_cr_tweak;
1942 corner_radius.sw = corner_radius.sw.at_least(min_outside_cr);
1943 }
1944 if original_cr.se == 0.0 {
1945 corner_radius.se = 0.0;
1946 } else {
1947 corner_radius.se += extra_cr_tweak;
1948 corner_radius.se = corner_radius.se.at_least(min_outside_cr);
1949 }
1950 }
1951
1952 let path = &mut self.scratchpad_path;
1953 path.clear();
1954 path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius);
1955 path.add_line_loop(&self.scratchpad_points);
1956
1957 let path_stroke = PathStroke::from(stroke).with_kind(stroke_kind);
1958
1959 if let Some(brush) = brush {
1960 let fill_rect = match stroke_kind {
1963 StrokeKind::Inside => rect.shrink(stroke.width),
1964 StrokeKind::Middle => rect.shrink(stroke.width / 2.0),
1965 StrokeKind::Outside => rect,
1966 };
1967
1968 if fill_rect.is_positive() {
1969 let crate::Brush {
1970 fill_texture_id,
1971 uv,
1972 } = **brush;
1973 let uv_from_pos = |p: Pos2| {
1974 pos2(
1975 remap(p.x, rect.x_range(), uv.x_range()),
1976 remap(p.y, rect.y_range(), uv.y_range()),
1977 )
1978 };
1979 path.fill_with_uv(self.feathering, fill, fill_texture_id, uv_from_pos, out);
1980 }
1981
1982 if !stroke.is_empty() {
1983 path.stroke_closed(self.feathering, &path_stroke, out);
1984 }
1985 } else {
1986 path.fill_and_stroke(self.feathering, fill, &path_stroke, out);
1988 }
1989
1990 self.feathering = old_feathering; }
1992
1993 pub fn tessellate_text(&mut self, text_shape: &TextShape, out: &mut Mesh) {
1997 let TextShape {
1998 pos: galley_pos,
1999 galley,
2000 underline,
2001 override_text_color,
2002 fallback_color,
2003 opacity_factor,
2004 angle,
2005 } = text_shape;
2006
2007 if galley.is_empty() {
2008 return;
2009 }
2010
2011 if *opacity_factor <= 0.0 {
2012 return;
2013 }
2014
2015 if galley.pixels_per_point != self.pixels_per_point {
2016 let warn = "epaint: WARNING: pixels_per_point (dpi scale) have changed between text layout and tessellation. \
2017 You must recreate your text shapes if pixels_per_point changes.";
2018 #[cfg(feature = "log")]
2019 log::warn!("{warn}");
2020 #[cfg(not(feature = "log"))]
2021 println!("{warn}");
2022 }
2023
2024 out.vertices.reserve(galley.num_vertices);
2025 out.indices.reserve(galley.num_indices);
2026
2027 let galley_pos = if self.options.round_text_to_pixels {
2030 galley_pos.round_to_pixels(self.pixels_per_point)
2031 } else {
2032 *galley_pos
2033 };
2034
2035 let uv_normalizer = vec2(
2036 1.0 / self.font_tex_size[0] as f32,
2037 1.0 / self.font_tex_size[1] as f32,
2038 );
2039
2040 let rotator = Rot2::from_angle(*angle);
2041
2042 for row in &galley.rows {
2043 if row.visuals.mesh.is_empty() {
2044 continue;
2045 }
2046
2047 let mut row_rect = row.visuals.mesh_bounds;
2048 if *angle != 0.0 {
2049 row_rect = row_rect.rotate_bb(rotator);
2050 }
2051 row_rect = row_rect.translate(galley_pos.to_vec2());
2052
2053 if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
2054 continue;
2057 }
2058
2059 let index_offset = out.vertices.len() as u32;
2060
2061 out.indices.extend(
2062 row.visuals
2063 .mesh
2064 .indices
2065 .iter()
2066 .map(|index| index + index_offset),
2067 );
2068
2069 out.vertices.extend(
2070 row.visuals
2071 .mesh
2072 .vertices
2073 .iter()
2074 .enumerate()
2075 .map(|(i, vertex)| {
2076 let Vertex { pos, uv, mut color } = *vertex;
2077
2078 if let Some(override_text_color) = override_text_color {
2079 if row.visuals.glyph_vertex_range.contains(&i) {
2081 color = *override_text_color;
2082 }
2083 } else if color == Color32::PLACEHOLDER {
2084 color = *fallback_color;
2085 }
2086
2087 if *opacity_factor < 1.0 {
2088 color = color.gamma_multiply(*opacity_factor);
2089 }
2090
2091 debug_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color.");
2092
2093 let offset = if *angle == 0.0 {
2094 pos.to_vec2()
2095 } else {
2096 rotator * pos.to_vec2()
2097 };
2098
2099 Vertex {
2100 pos: galley_pos + offset,
2101 uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
2102 color,
2103 }
2104 }),
2105 );
2106
2107 if *underline != Stroke::NONE {
2108 self.tessellate_line_segment(
2109 [row_rect.left_bottom(), row_rect.right_bottom()],
2110 *underline,
2111 out,
2112 );
2113 }
2114 }
2115 }
2116
2117 pub fn tessellate_quadratic_bezier(
2122 &mut self,
2123 quadratic_shape: &QuadraticBezierShape,
2124 out: &mut Mesh,
2125 ) {
2126 let options = &self.options;
2127 let clip_rect = self.clip_rect;
2128
2129 if options.coarse_tessellation_culling
2130 && !quadratic_shape.visual_bounding_rect().intersects(clip_rect)
2131 {
2132 return;
2133 }
2134
2135 let points = quadratic_shape.flatten(Some(options.bezier_tolerance));
2136
2137 self.tessellate_bezier_complete(
2138 &points,
2139 quadratic_shape.fill,
2140 quadratic_shape.closed,
2141 &quadratic_shape.stroke,
2142 out,
2143 );
2144 }
2145
2146 pub fn tessellate_cubic_bezier(&mut self, cubic_shape: &CubicBezierShape, out: &mut Mesh) {
2151 let options = &self.options;
2152 let clip_rect = self.clip_rect;
2153 if options.coarse_tessellation_culling
2154 && !cubic_shape.visual_bounding_rect().intersects(clip_rect)
2155 {
2156 return;
2157 }
2158
2159 let points_vec =
2160 cubic_shape.flatten_closed(Some(options.bezier_tolerance), Some(options.epsilon));
2161
2162 for points in points_vec {
2163 self.tessellate_bezier_complete(
2164 &points,
2165 cubic_shape.fill,
2166 cubic_shape.closed,
2167 &cubic_shape.stroke,
2168 out,
2169 );
2170 }
2171 }
2172
2173 fn tessellate_bezier_complete(
2174 &mut self,
2175 points: &[Pos2],
2176 fill: Color32,
2177 closed: bool,
2178 stroke: &PathStroke,
2179 out: &mut Mesh,
2180 ) {
2181 if points.len() < 2 {
2182 return;
2183 }
2184
2185 self.scratchpad_path.clear();
2186 if closed {
2187 self.scratchpad_path.add_line_loop(points);
2188
2189 self.scratchpad_path
2190 .fill_and_stroke(self.feathering, fill, stroke, out);
2191 } else {
2192 debug_assert_eq!(
2193 fill,
2194 Color32::TRANSPARENT,
2195 "You asked to fill a bezier path that is not closed. That makes no sense."
2196 );
2197
2198 self.scratchpad_path.add_open_points(points);
2199
2200 self.scratchpad_path
2201 .stroke(self.feathering, PathType::Open, stroke, out);
2202 }
2203 }
2204}
2205
2206fn round_line_segment(coord: &mut f32, stroke: &Stroke, pixels_per_point: f32) {
2207 let pixel_size = 1.0 / pixels_per_point;
2220
2221 if stroke.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * stroke.width) {
2222 *coord = coord.round_to_pixel_center(pixels_per_point);
2223 } else {
2224 *coord = coord.round_to_pixels(pixels_per_point);
2225 }
2226}
2227
2228fn is_nearest_integer_odd(width: f32) -> bool {
2229 (width * 0.5 + 0.25).fract() > 0.5
2230}
2231
2232#[test]
2233fn test_is_nearest_integer_odd() {
2234 assert!(is_nearest_integer_odd(0.6));
2235 assert!(is_nearest_integer_odd(1.0));
2236 assert!(is_nearest_integer_odd(1.4));
2237 assert!(!is_nearest_integer_odd(1.6));
2238 assert!(!is_nearest_integer_odd(2.0));
2239 assert!(!is_nearest_integer_odd(2.4));
2240 assert!(is_nearest_integer_odd(2.6));
2241 assert!(is_nearest_integer_odd(3.0));
2242 assert!(is_nearest_integer_odd(3.4));
2243}
2244
2245#[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"]
2246pub fn tessellate_shapes(
2247 pixels_per_point: f32,
2248 options: TessellationOptions,
2249 font_tex_size: [usize; 2],
2250 prepared_discs: Vec<PreparedDisc>,
2251 shapes: Vec<ClippedShape>,
2252) -> Vec<ClippedPrimitive> {
2253 Tessellator::new(pixels_per_point, options, font_tex_size, prepared_discs)
2254 .tessellate_shapes(shapes)
2255}
2256
2257impl Tessellator {
2258 #[allow(unused_mut)]
2274 pub fn tessellate_shapes(&mut self, mut shapes: Vec<ClippedShape>) -> Vec<ClippedPrimitive> {
2275 profiling::function_scope!();
2276
2277 #[cfg(feature = "rayon")]
2278 if self.options.parallel_tessellation {
2279 self.parallel_tessellation_of_large_shapes(&mut shapes);
2280 }
2281
2282 let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
2283
2284 {
2285 profiling::scope!("tessellate");
2286 for clipped_shape in shapes {
2287 self.tessellate_clipped_shape(clipped_shape, &mut clipped_primitives);
2288 }
2289 }
2290
2291 if self.options.debug_paint_clip_rects {
2292 clipped_primitives = self.add_clip_rects(clipped_primitives);
2293 }
2294
2295 if self.options.debug_ignore_clip_rects {
2296 for clipped_primitive in &mut clipped_primitives {
2297 clipped_primitive.clip_rect = Rect::EVERYTHING;
2298 }
2299 }
2300
2301 clipped_primitives.retain(|p| {
2302 p.clip_rect.is_positive()
2303 && match &p.primitive {
2304 Primitive::Mesh(mesh) => !mesh.is_empty(),
2305 Primitive::Callback(_) => true,
2306 }
2307 });
2308
2309 for clipped_primitive in &clipped_primitives {
2310 if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {
2311 debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
2312 }
2313 }
2314
2315 clipped_primitives
2316 }
2317
2318 #[cfg(feature = "rayon")]
2321 fn parallel_tessellation_of_large_shapes(&self, shapes: &mut [ClippedShape]) {
2322 profiling::function_scope!();
2323
2324 use rayon::prelude::*;
2325
2326 fn should_parallelize(shape: &Shape) -> bool {
2330 match shape {
2331 Shape::Vec(shapes) => 4 < shapes.len() || shapes.iter().any(should_parallelize),
2332
2333 Shape::Path(path_shape) => 32 < path_shape.points.len(),
2334
2335 Shape::QuadraticBezier(_) | Shape::CubicBezier(_) | Shape::Ellipse(_) => true,
2336
2337 Shape::Noop
2338 | Shape::Text(_)
2339 | Shape::Circle(_)
2340 | Shape::Mesh(_)
2341 | Shape::LineSegment { .. }
2342 | Shape::Rect(_)
2343 | Shape::Callback(_) => false,
2344 }
2345 }
2346
2347 let tessellated: Vec<(usize, Mesh)> = shapes
2348 .par_iter()
2349 .enumerate()
2350 .filter(|(_, clipped_shape)| should_parallelize(&clipped_shape.shape))
2351 .map(|(index, clipped_shape)| {
2352 profiling::scope!("tessellate_big_shape");
2353 let mut tessellator = (*self).clone();
2355 let mut mesh = Mesh::default();
2356 tessellator.tessellate_shape(clipped_shape.shape.clone(), &mut mesh);
2357 (index, mesh)
2358 })
2359 .collect();
2360
2361 profiling::scope!("distribute results", tessellated.len().to_string());
2362 for (index, mesh) in tessellated {
2363 shapes[index].shape = Shape::Mesh(mesh.into());
2364 }
2365 }
2366
2367 fn add_clip_rects(
2368 &mut self,
2369 clipped_primitives: Vec<ClippedPrimitive>,
2370 ) -> Vec<ClippedPrimitive> {
2371 self.clip_rect = Rect::EVERYTHING;
2372 let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150));
2373
2374 clipped_primitives
2375 .into_iter()
2376 .flat_map(|clipped_primitive| {
2377 let mut clip_rect_mesh = Mesh::default();
2378 self.tessellate_shape(
2379 Shape::rect_stroke(
2380 clipped_primitive.clip_rect,
2381 0.0,
2382 stroke,
2383 StrokeKind::Outside,
2384 ),
2385 &mut clip_rect_mesh,
2386 );
2387
2388 [
2389 clipped_primitive,
2390 ClippedPrimitive {
2391 clip_rect: Rect::EVERYTHING, primitive: Primitive::Mesh(clip_rect_mesh),
2393 },
2394 ]
2395 })
2396 .collect()
2397 }
2398}
2399
2400#[test]
2401fn test_tessellator() {
2402 use crate::*;
2403
2404 let mut shapes = Vec::with_capacity(2);
2405
2406 let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
2407 let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
2408
2409 let mut mesh = Mesh::with_texture(TextureId::Managed(1));
2410 mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
2411 shapes.push(Shape::mesh(mesh));
2412
2413 let mut mesh = Mesh::with_texture(TextureId::Managed(2));
2414 mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
2415 shapes.push(Shape::mesh(mesh));
2416
2417 let shape = Shape::Vec(shapes);
2418 let clipped_shapes = vec![ClippedShape {
2419 clip_rect: rect,
2420 shape,
2421 }];
2422
2423 let font_tex_size = [1024, 1024]; let prepared_discs = vec![]; let primitives = Tessellator::new(1.0, Default::default(), font_tex_size, prepared_discs)
2427 .tessellate_shapes(clipped_shapes);
2428
2429 assert_eq!(primitives.len(), 2);
2430}
2431
2432#[test]
2433fn path_bounding_box() {
2434 use crate::*;
2435
2436 for i in 1..=100 {
2437 let width = i as f32;
2438
2439 let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(10.0, 10.0));
2440 let expected_rect = rect.expand((width / 2.0) + 1.5);
2441
2442 let mut mesh = Mesh::default();
2443
2444 let mut path = Path::default();
2445 path.add_open_points(&[
2446 pos2(0.0, 0.0),
2447 pos2(2.0, 0.0),
2448 pos2(5.0, 5.0),
2449 pos2(0.0, 5.0),
2450 pos2(0.0, 7.0),
2451 pos2(10.0, 10.0),
2452 ]);
2453
2454 path.stroke(
2455 1.5,
2456 PathType::Closed,
2457 &PathStroke::new_uv(width, move |r, p| {
2458 assert_eq!(r, expected_rect);
2459 assert!(
2462 r.distance_to_pos(p) <= 0.55,
2463 "passed rect {r:?} didn't contain point {p:?} (distance: {})",
2464 r.distance_to_pos(p)
2465 );
2466 assert!(
2467 expected_rect.distance_to_pos(p) <= 0.55,
2468 "expected rect {expected_rect:?} didn't contain point {p:?}"
2469 );
2470 Color32::WHITE
2471 }),
2472 &mut mesh,
2473 );
2474 }
2475}