Skip to content

Commit

Permalink
Fix anti-aliasing of filled paths with counter-clockwise winding order
Browse files Browse the repository at this point in the history
Part of #1226
  • Loading branch information
emilk committed Feb 19, 2022
1 parent 10634fc commit b8fbbf7
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 20 deletions.
6 changes: 3 additions & 3 deletions egui/src/widgets/color_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
23 changes: 14 additions & 9 deletions egui/src/widgets/plot/items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,9 +807,9 @@ impl Points {

impl PlotItem for Points {
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
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,
Expand Down Expand Up @@ -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));
}
Expand All @@ -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 => {
Expand All @@ -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));
}
Expand Down
1 change: 1 addition & 0 deletions epaint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion epaint/src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pos2>,
Expand Down Expand Up @@ -259,6 +261,7 @@ impl From<CircleShape> 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<Pos2>,
/// If true, connect the first and last of the points together.
/// This is required if `fill != TRANSPARENT`.
Expand Down Expand Up @@ -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<Pos2>,
Expand Down Expand Up @@ -455,7 +460,7 @@ pub struct TextShape {
/// This will NOT replace background color nor strikethrough/underline color.
pub override_text_color: Option<Color32>,

/// 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,
}
Expand Down
44 changes: 37 additions & 7 deletions epaint/src/tessellator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down

0 comments on commit b8fbbf7

Please sign in to comment.