diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index 278d6ebfa29..a20336772d6 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to the epaint crate will be documented in this file. * MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). * Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)). * Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)). +* Optimize tessellation of circles and boxes with rounded corners ([#1547](https://github.com/emilk/egui/pull/1547)). ## 0.17.0 - 2022-02-22 diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 980abee5dcc..f6e84a5d1c7 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -7,7 +7,293 @@ use crate::*; use emath::*; -use std::f32::consts::TAU; + +// ---------------------------------------------------------------------------- + +#[allow(clippy::approx_constant)] +mod precomputed_vertices { + /* + fn main() { + let n = 64; + println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1); + for i in 0..=n { + let a = std::f64::consts::TAU * i as f64 / n as f64; + println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin()); + } + println!("];") + } + */ + + use emath::{vec2, Vec2}; + + pub const CIRCLE_8: [Vec2; 9] = [ + vec2(1.000000, 0.000000), + vec2(0.707107, 0.707107), + vec2(0.000000, 1.000000), + vec2(-0.707107, 0.707107), + vec2(-1.000000, 0.000000), + vec2(-0.707107, -0.707107), + vec2(0.000000, -1.000000), + vec2(0.707107, -0.707107), + vec2(1.000000, 0.000000), + ]; + + pub const CIRCLE_16: [Vec2; 17] = [ + vec2(1.000000, 0.000000), + vec2(0.923880, 0.382683), + vec2(0.707107, 0.707107), + vec2(0.382683, 0.923880), + vec2(0.000000, 1.000000), + vec2(-0.382684, 0.923880), + vec2(-0.707107, 0.707107), + vec2(-0.923880, 0.382683), + vec2(-1.000000, 0.000000), + vec2(-0.923880, -0.382683), + vec2(-0.707107, -0.707107), + vec2(-0.382684, -0.923880), + vec2(0.000000, -1.000000), + vec2(0.382684, -0.923879), + vec2(0.707107, -0.707107), + vec2(0.923880, -0.382683), + vec2(1.000000, 0.000000), + ]; + + pub const CIRCLE_32: [Vec2; 33] = [ + vec2(1.000000, 0.000000), + vec2(0.980785, 0.195090), + vec2(0.923880, 0.382683), + vec2(0.831470, 0.555570), + vec2(0.707107, 0.707107), + vec2(0.555570, 0.831470), + vec2(0.382683, 0.923880), + vec2(0.195090, 0.980785), + vec2(0.000000, 1.000000), + vec2(-0.195090, 0.980785), + vec2(-0.382683, 0.923880), + vec2(-0.555570, 0.831470), + vec2(-0.707107, 0.707107), + vec2(-0.831470, 0.555570), + vec2(-0.923880, 0.382683), + vec2(-0.980785, 0.195090), + vec2(-1.000000, 0.000000), + vec2(-0.980785, -0.195090), + vec2(-0.923880, -0.382683), + vec2(-0.831470, -0.555570), + vec2(-0.707107, -0.707107), + vec2(-0.555570, -0.831470), + vec2(-0.382683, -0.923880), + vec2(-0.195090, -0.980785), + vec2(-0.000000, -1.000000), + vec2(0.195090, -0.980785), + vec2(0.382683, -0.923880), + vec2(0.555570, -0.831470), + vec2(0.707107, -0.707107), + vec2(0.831470, -0.555570), + vec2(0.923880, -0.382683), + vec2(0.980785, -0.195090), + vec2(1.000000, -0.000000), + ]; + + pub const CIRCLE_64: [Vec2; 65] = [ + vec2(1.000000, 0.000000), + vec2(0.995185, 0.098017), + vec2(0.980785, 0.195090), + vec2(0.956940, 0.290285), + vec2(0.923880, 0.382683), + vec2(0.881921, 0.471397), + vec2(0.831470, 0.555570), + vec2(0.773010, 0.634393), + vec2(0.707107, 0.707107), + vec2(0.634393, 0.773010), + vec2(0.555570, 0.831470), + vec2(0.471397, 0.881921), + vec2(0.382683, 0.923880), + vec2(0.290285, 0.956940), + vec2(0.195090, 0.980785), + vec2(0.098017, 0.995185), + vec2(0.000000, 1.000000), + vec2(-0.098017, 0.995185), + vec2(-0.195090, 0.980785), + vec2(-0.290285, 0.956940), + vec2(-0.382683, 0.923880), + vec2(-0.471397, 0.881921), + vec2(-0.555570, 0.831470), + vec2(-0.634393, 0.773010), + vec2(-0.707107, 0.707107), + vec2(-0.773010, 0.634393), + vec2(-0.831470, 0.555570), + vec2(-0.881921, 0.471397), + vec2(-0.923880, 0.382683), + vec2(-0.956940, 0.290285), + vec2(-0.980785, 0.195090), + vec2(-0.995185, 0.098017), + vec2(-1.000000, 0.000000), + vec2(-0.995185, -0.098017), + vec2(-0.980785, -0.195090), + vec2(-0.956940, -0.290285), + vec2(-0.923880, -0.382683), + vec2(-0.881921, -0.471397), + vec2(-0.831470, -0.555570), + vec2(-0.773010, -0.634393), + vec2(-0.707107, -0.707107), + vec2(-0.634393, -0.773010), + vec2(-0.555570, -0.831470), + vec2(-0.471397, -0.881921), + vec2(-0.382683, -0.923880), + vec2(-0.290285, -0.956940), + vec2(-0.195090, -0.980785), + vec2(-0.098017, -0.995185), + vec2(-0.000000, -1.000000), + vec2(0.098017, -0.995185), + vec2(0.195090, -0.980785), + vec2(0.290285, -0.956940), + vec2(0.382683, -0.923880), + vec2(0.471397, -0.881921), + vec2(0.555570, -0.831470), + vec2(0.634393, -0.773010), + vec2(0.707107, -0.707107), + vec2(0.773010, -0.634393), + vec2(0.831470, -0.555570), + vec2(0.881921, -0.471397), + vec2(0.923880, -0.382683), + vec2(0.956940, -0.290285), + vec2(0.980785, -0.195090), + vec2(0.995185, -0.098017), + vec2(1.000000, -0.000000), + ]; + + pub const CIRCLE_128: [Vec2; 129] = [ + vec2(1.000000, 0.000000), + vec2(0.998795, 0.049068), + vec2(0.995185, 0.098017), + vec2(0.989177, 0.146730), + vec2(0.980785, 0.195090), + vec2(0.970031, 0.242980), + vec2(0.956940, 0.290285), + vec2(0.941544, 0.336890), + vec2(0.923880, 0.382683), + vec2(0.903989, 0.427555), + vec2(0.881921, 0.471397), + vec2(0.857729, 0.514103), + vec2(0.831470, 0.555570), + vec2(0.803208, 0.595699), + vec2(0.773010, 0.634393), + vec2(0.740951, 0.671559), + vec2(0.707107, 0.707107), + vec2(0.671559, 0.740951), + vec2(0.634393, 0.773010), + vec2(0.595699, 0.803208), + vec2(0.555570, 0.831470), + vec2(0.514103, 0.857729), + vec2(0.471397, 0.881921), + vec2(0.427555, 0.903989), + vec2(0.382683, 0.923880), + vec2(0.336890, 0.941544), + vec2(0.290285, 0.956940), + vec2(0.242980, 0.970031), + vec2(0.195090, 0.980785), + vec2(0.146730, 0.989177), + vec2(0.098017, 0.995185), + vec2(0.049068, 0.998795), + vec2(0.000000, 1.000000), + vec2(-0.049068, 0.998795), + vec2(-0.098017, 0.995185), + vec2(-0.146730, 0.989177), + vec2(-0.195090, 0.980785), + vec2(-0.242980, 0.970031), + vec2(-0.290285, 0.956940), + vec2(-0.336890, 0.941544), + vec2(-0.382683, 0.923880), + vec2(-0.427555, 0.903989), + vec2(-0.471397, 0.881921), + vec2(-0.514103, 0.857729), + vec2(-0.555570, 0.831470), + vec2(-0.595699, 0.803208), + vec2(-0.634393, 0.773010), + vec2(-0.671559, 0.740951), + vec2(-0.707107, 0.707107), + vec2(-0.740951, 0.671559), + vec2(-0.773010, 0.634393), + vec2(-0.803208, 0.595699), + vec2(-0.831470, 0.555570), + vec2(-0.857729, 0.514103), + vec2(-0.881921, 0.471397), + vec2(-0.903989, 0.427555), + vec2(-0.923880, 0.382683), + vec2(-0.941544, 0.336890), + vec2(-0.956940, 0.290285), + vec2(-0.970031, 0.242980), + vec2(-0.980785, 0.195090), + vec2(-0.989177, 0.146730), + vec2(-0.995185, 0.098017), + vec2(-0.998795, 0.049068), + vec2(-1.000000, 0.000000), + vec2(-0.998795, -0.049068), + vec2(-0.995185, -0.098017), + vec2(-0.989177, -0.146730), + vec2(-0.980785, -0.195090), + vec2(-0.970031, -0.242980), + vec2(-0.956940, -0.290285), + vec2(-0.941544, -0.336890), + vec2(-0.923880, -0.382683), + vec2(-0.903989, -0.427555), + vec2(-0.881921, -0.471397), + vec2(-0.857729, -0.514103), + vec2(-0.831470, -0.555570), + vec2(-0.803208, -0.595699), + vec2(-0.773010, -0.634393), + vec2(-0.740951, -0.671559), + vec2(-0.707107, -0.707107), + vec2(-0.671559, -0.740951), + vec2(-0.634393, -0.773010), + vec2(-0.595699, -0.803208), + vec2(-0.555570, -0.831470), + vec2(-0.514103, -0.857729), + vec2(-0.471397, -0.881921), + vec2(-0.427555, -0.903989), + vec2(-0.382683, -0.923880), + vec2(-0.336890, -0.941544), + vec2(-0.290285, -0.956940), + vec2(-0.242980, -0.970031), + vec2(-0.195090, -0.980785), + vec2(-0.146730, -0.989177), + vec2(-0.098017, -0.995185), + vec2(-0.049068, -0.998795), + vec2(-0.000000, -1.000000), + vec2(0.049068, -0.998795), + vec2(0.098017, -0.995185), + vec2(0.146730, -0.989177), + vec2(0.195090, -0.980785), + vec2(0.242980, -0.970031), + vec2(0.290285, -0.956940), + vec2(0.336890, -0.941544), + vec2(0.382683, -0.923880), + vec2(0.427555, -0.903989), + vec2(0.471397, -0.881921), + vec2(0.514103, -0.857729), + vec2(0.555570, -0.831470), + vec2(0.595699, -0.803208), + vec2(0.634393, -0.773010), + vec2(0.671559, -0.740951), + vec2(0.707107, -0.707107), + vec2(0.740951, -0.671559), + vec2(0.773010, -0.634393), + vec2(0.803208, -0.595699), + vec2(0.831470, -0.555570), + vec2(0.857729, -0.514103), + vec2(0.881921, -0.471397), + vec2(0.903989, -0.427555), + vec2(0.923880, -0.382683), + vec2(0.941544, -0.336890), + vec2(0.956940, -0.290285), + vec2(0.970031, -0.242980), + vec2(0.980785, -0.195090), + vec2(0.989177, -0.146730), + vec2(0.995185, -0.098017), + vec2(0.998795, -0.049068), + vec2(1.000000, -0.000000), + ]; +} // ---------------------------------------------------------------------------- @@ -50,13 +336,36 @@ impl Path { } pub fn add_circle(&mut self, center: Pos2, radius: f32) { - let n = (radius * 4.0).round() as i32; // TODO: tweak a bit more - let n = n.clamp(4, 64); - self.reserve(n as usize); - for i in 0..n { - let angle = remap(i as f32, 0.0..=n as f32, 0.0..=TAU); - let normal = vec2(angle.cos(), angle.sin()); - self.add_point(center + radius * normal, normal); + use precomputed_vertices::*; + + // These cutoffs are based on a high-dpi display. TODO: use pixels_per_point here? + // same cutoffs as in add_circle_quadrant + + if radius <= 2.0 { + self.0.extend(CIRCLE_8.iter().map(|&n| PathPoint { + pos: center + radius * n, + normal: n, + })); + } else if radius <= 5.0 { + self.0.extend(CIRCLE_16.iter().map(|&n| PathPoint { + pos: center + radius * n, + normal: n, + })); + } else if radius < 18.0 { + self.0.extend(CIRCLE_32.iter().map(|&n| PathPoint { + pos: center + radius * n, + normal: n, + })); + } else if radius < 50.0 { + self.0.extend(CIRCLE_64.iter().map(|&n| PathPoint { + pos: center + radius * n, + normal: n, + })); + } else { + self.0.extend(CIRCLE_128.iter().map(|&n| PathPoint { + pos: center + radius * n, + normal: n, + })); } } @@ -189,8 +498,7 @@ impl Path { pub mod path { //! Helpers for constructing paths use crate::shape::Rounding; - - use super::*; + use emath::*; /// overwrites existing points pub fn rounded_rectangle(path: &mut Vec, rect: Rect, rounding: Rounding) { @@ -236,19 +544,33 @@ pub mod path { // - quadrant 3: right top // * angle 4 * TAU / 4 = right pub fn add_circle_quadrant(path: &mut Vec, center: Pos2, radius: f32, quadrant: f32) { - // TODO: optimize with precalculated vertices for some radii ranges + use super::precomputed_vertices::*; - let n = (radius * 0.75).round() as i32; // TODO: tweak a bit more - let n = n.clamp(2, 32); - const RIGHT_ANGLE: f32 = TAU / 4.0; - path.reserve(n as usize + 1); - for i in 0..=n { - let angle = remap( - i as f32, - 0.0..=n as f32, - quadrant * RIGHT_ANGLE..=(quadrant + 1.0) * RIGHT_ANGLE, - ); - path.push(center + radius * Vec2::angled(angle)); + // These cutoffs are based on a high-dpi display. TODO: use pixels_per_point here? + // same cutoffs as in add_circle + + if radius <= 0.0 { + path.push(center); + } else if radius <= 2.0 { + let offset = quadrant as usize * 2; + let quadrant_vertices = &CIRCLE_8[offset..=offset + 2]; + path.extend(quadrant_vertices.iter().map(|&n| center + radius * n)); + } else if radius <= 5.0 { + let offset = quadrant as usize * 4; + let quadrant_vertices = &CIRCLE_16[offset..=offset + 4]; + path.extend(quadrant_vertices.iter().map(|&n| center + radius * n)); + } else if radius < 18.0 { + let offset = quadrant as usize * 8; + let quadrant_vertices = &CIRCLE_32[offset..=offset + 8]; + path.extend(quadrant_vertices.iter().map(|&n| center + radius * n)); + } else if radius < 50.0 { + let offset = quadrant as usize * 16; + let quadrant_vertices = &CIRCLE_64[offset..=offset + 16]; + path.extend(quadrant_vertices.iter().map(|&n| center + radius * n)); + } else { + let offset = quadrant as usize * 32; + let quadrant_vertices = &CIRCLE_128[offset..=offset + 32]; + path.extend(quadrant_vertices.iter().map(|&n| center + radius * n)); } }