diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 22fcab06..4a5f4c2a 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -145,9 +145,9 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color let picked_color = color_at(*value); ui.painter().add(Shape::convex_polygon( vec![ - pos2(x - r, rect.bottom()), - pos2(x + r, rect.bottom()), - pos2(x, rect.center().y), + pos2(x, rect.center().y), // tip + pos2(x + r, rect.bottom()), // right bottom + pos2(x - r, rect.bottom()), // left bottom ], picked_color, Stroke::new(visuals.fg_stroke.width, contrast_color(picked_color)), diff --git a/egui/src/widgets/plot/items/mod.rs b/egui/src/widgets/plot/items/mod.rs index 2a00a285..eeb8180a 100644 --- a/egui/src/widgets/plot/items/mod.rs +++ b/egui/src/widgets/plot/items/mod.rs @@ -807,9 +807,9 @@ impl Points { impl PlotItem for Points { fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { - let sqrt_3 = 3f32.sqrt(); - let frac_sqrt_3_2 = 3f32.sqrt() / 2.0; - let frac_1_sqrt_2 = 1.0 / 2f32.sqrt(); + let sqrt_3 = 3_f32.sqrt(); + let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0; + let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt(); let Self { series, @@ -861,15 +861,20 @@ impl PlotItem for Points { })); } MarkerShape::Diamond => { - let points = vec![tf(1.0, 0.0), tf(0.0, -1.0), tf(-1.0, 0.0), tf(0.0, 1.0)]; + let points = vec![ + tf(0.0, 1.0), // bottom + tf(-1.0, 0.0), // left + tf(0.0, -1.0), // top + tf(1.0, 0.0), // right + ]; shapes.push(Shape::convex_polygon(points, fill, stroke)); } MarkerShape::Square => { let points = vec![ - tf(frac_1_sqrt_2, frac_1_sqrt_2), - tf(frac_1_sqrt_2, -frac_1_sqrt_2), - tf(-frac_1_sqrt_2, -frac_1_sqrt_2), tf(-frac_1_sqrt_2, frac_1_sqrt_2), + tf(-frac_1_sqrt_2, -frac_1_sqrt_2), + tf(frac_1_sqrt_2, -frac_1_sqrt_2), + tf(frac_1_sqrt_2, frac_1_sqrt_2), ]; shapes.push(Shape::convex_polygon(points, fill, stroke)); } @@ -893,7 +898,7 @@ impl PlotItem for Points { } MarkerShape::Up => { let points = - vec![tf(0.0, -1.0), tf(-0.5 * sqrt_3, 0.5), tf(0.5 * sqrt_3, 0.5)]; + vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)]; shapes.push(Shape::convex_polygon(points, fill, stroke)); } MarkerShape::Down => { @@ -912,8 +917,8 @@ impl PlotItem for Points { MarkerShape::Right => { let points = vec![ tf(1.0, 0.0), - tf(-0.5, -0.5 * sqrt_3), tf(-0.5, 0.5 * sqrt_3), + tf(-0.5, -0.5 * sqrt_3), ]; shapes.push(Shape::convex_polygon(points, fill, stroke)); } diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index 0039d9e5..62d034dc 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to the epaint crate will be documented in this file. * Added `ImageData` and `TextureManager` for loading images into textures ([#1110](https://github.com/emilk/egui/pull/1110)). * Added `Shape::dashed_line_many` ([#1027](https://github.com/emilk/egui/pull/1027)). * Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)). +* Fix anti-aliasing of filled paths with counter-clockwise winding order. ## 0.16.0 - 2021-12-29 diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 7ac5e828..6d745eee 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -94,6 +94,8 @@ impl Shape { } /// A convex polygon with a fill and optional stroke. + /// + /// The most performant winding order is clockwise. #[inline] pub fn convex_polygon( points: Vec, @@ -259,6 +261,7 @@ impl From for Shape { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct PathShape { + /// Filled paths should prefer clockwise order. pub points: Vec, /// If true, connect the first and last of the points together. /// This is required if `fill != TRANSPARENT`. @@ -294,6 +297,8 @@ impl PathShape { } /// A convex polygon with a fill and optional stroke. + /// + /// The most performant winding order is clockwise. #[inline] pub fn convex_polygon( points: Vec, @@ -455,7 +460,7 @@ pub struct TextShape { /// This will NOT replace background color nor strikethrough/underline color. pub override_text_color: Option, - /// Rotate text by this many radians clock-wise. + /// Rotate text by this many radians clockwise. /// The pivot is `pos` (the upper left corner of the text). pub angle: f32, } diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index ec16b3ad..4ffe11dc 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -172,8 +172,12 @@ impl Path { } /// The path is taken to be closed (i.e. returning to the start again). - pub fn fill(&self, color: Color32, options: &TessellationOptions, out: &mut Mesh) { - fill_closed_path(&self.0, color, options, out); + /// + /// Calling this may reverse the vertices in the path if they are wrong winding order. + /// + /// The preferred winding order is clockwise. + pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) { + fill_closed_path(&mut self.0, color, options, out); } } @@ -196,10 +200,10 @@ pub mod path { let min = rect.min; let max = rect.max; path.reserve(4); - 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)); + path.push(pos2(min.x, min.y)); // left top + path.push(pos2(max.x, min.y)); // right top + path.push(pos2(max.x, max.y)); // right bottom + path.push(pos2(min.x, max.y)); // left bottom } else { add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); @@ -346,9 +350,27 @@ impl TessellationOptions { } } +fn cw_signed_area(path: &[PathPoint]) -> f64 { + if let Some(last) = path.last() { + let mut previous = last.pos; + let mut area = 0.0; + for p in path { + area += (previous.x * p.pos.y - p.pos.x * previous.y) as f64; + previous = p.pos; + } + area + } else { + 0.0 + } +} + /// Tessellate the given convex area into a polygon. +/// +/// Calling this may reverse the vertices in the path if they are wrong winding order. +/// +/// The preferred winding order is clockwise. fn fill_closed_path( - path: &[PathPoint], + path: &mut [PathPoint], color: Color32, options: &TessellationOptions, out: &mut Mesh, @@ -359,6 +381,14 @@ fn fill_closed_path( let n = path.len() as u32; if options.anti_alias { + if cw_signed_area(path) < 0.0 { + // Wrong winding order - fix: + path.reverse(); + for point in path.iter_mut() { + point.normal = -point.normal; + } + } + out.reserve_triangles(3 * n as usize); out.reserve_vertices(2 * n as usize); let color_outer = Color32::TRANSPARENT;