Fix anti-aliasing of filled paths with counter-clockwise winding order
Part of https://github.com/emilk/egui/issues/1226
This commit is contained in:
parent
10634fc344
commit
b8fbbf7d62
5 changed files with 61 additions and 20 deletions
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
@ -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`.
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue