Start refactor mesher module into a more reusable Path concept

This commit is contained in:
Emil Ernerfeldt 2020-04-19 00:27:25 +02:00
parent 7480191f1a
commit 6eb1053c35
2 changed files with 264 additions and 218 deletions

View file

@ -57,7 +57,7 @@ impl Emigui {
pub fn paint(&mut self) -> Mesh { pub fn paint(&mut self) -> Mesh {
let paint_commands = self.ctx.drain_paint_lists(); let paint_commands = self.ctx.drain_paint_lists();
let mut mesher = Mesher::new(self.last_input.pixels_per_point); let mut mesher = Mesher::new(self.last_input.pixels_per_point);
mesher.anti_alias = self.anti_alias; mesher.options.anti_alias = self.anti_alias;
mesher.paint(&self.ctx.fonts, &paint_commands); mesher.paint(&self.ctx.fonts, &paint_commands);
let mesh = mesher.mesh; let mesh = mesher.mesh;

View file

@ -4,7 +4,7 @@
use crate::{ use crate::{
color::Color, color::Color,
fonts::Fonts, fonts::Fonts,
math::{remap, vec2, Vec2, TAU}, math::{remap, vec2, Rect, Vec2, TAU},
types::PaintCmd, types::PaintCmd,
}; };
@ -20,6 +20,8 @@ pub struct Vertex {
pub color: Color, pub color: Color,
} }
// ----------------------------------------------------------------------------
#[derive(Clone, Debug, Default, Serialize)] #[derive(Clone, Debug, Default, Serialize)]
pub struct Mesh { pub struct Mesh {
/// Draw as triangles (i.e. the length is a multiple of three) /// Draw as triangles (i.e. the length is a multiple of three)
@ -119,6 +121,104 @@ impl Mesh {
} }
} }
// ----------------------------------------------------------------------------
pub struct PathPoint {
pos: Vec2,
/// For filled paths the normal is used for antialiasing.
/// For outlines the normal is used for figuring out how to make the line wide
/// (i.e. in what direction to expand).
/// The normal could be estimated by differences between successive points,
/// but that would be less accurate (and in some cases slower).
normal: Vec2,
}
#[derive(Default)]
struct Path(Vec<PathPoint>);
impl Path {
pub fn clear(&mut self) {
self.0.clear();
}
pub fn add_point(&mut self, pos: Vec2, normal: Vec2) {
self.0.push(PathPoint { pos, normal });
}
pub fn add_circle(&mut self, center: Vec2, radius: f32) {
let n = 32; // TODO: parameter
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);
}
}
pub fn add_line(&mut self, points: &[Vec2]) {
let n = points.len();
assert!(n >= 2);
self.add_point(points[0], (points[1] - points[0]).normalized().rot90());
for i in 1..n - 1 {
let n0 = (points[i] - points[i - 1]).normalized().rot90();
let n1 = (points[i + 1] - points[i]).normalized().rot90();
let v = (n0 + n1) / 2.0;
let normal = v / v.length_sq();
self.add_point(points[i], normal); // TODO: handle VERY sharp turns better
}
self.add_point(
points[n - 1],
(points[n - 1] - points[n - 2]).normalized().rot90(),
);
}
pub fn add_rectangle(&mut self, rect: &Rect) {
let min = rect.min();
let max = rect.max();
self.add_point(vec2(min.x, min.y), vec2(-1.0, -1.0));
self.add_point(vec2(max.x, min.y), vec2(1.0, -1.0));
self.add_point(vec2(max.x, max.y), vec2(1.0, 1.0));
self.add_point(vec2(min.x, max.y), vec2(-1.0, 1.0));
}
pub fn add_rounded_rectangle(&mut self, rect: &Rect, corner_radius: f32) {
let min = rect.min();
let max = rect.max();
let cr = corner_radius
.min(rect.width() * 0.5)
.min(rect.height() * 0.5);
if cr <= 0.0 {
self.add_rectangle(rect);
} else {
self.add_circle_quadrant(vec2(max.x - cr, max.y - cr), cr, 0.0);
self.add_circle_quadrant(vec2(min.x + cr, max.y - cr), cr, 1.0);
self.add_circle_quadrant(vec2(min.x + cr, min.y + cr), cr, 2.0);
self.add_circle_quadrant(vec2(max.x - cr, min.y + cr), cr, 3.0);
}
}
pub fn add_circle_quadrant(&mut self, center: Vec2, radius: f32, quadrant: f32) {
let n = 8;
const RIGHT_ANGLE: f32 = TAU / 4.0;
for i in 0..=n {
let angle = remap(
i as f32,
0.0,
n as f32,
quadrant * RIGHT_ANGLE,
(quadrant + 1.0) * RIGHT_ANGLE,
);
let normal = vec2(angle.cos(), angle.sin());
self.add_point(center + radius * normal, normal);
}
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum PathType { pub enum PathType {
Open, Open,
@ -126,33 +226,24 @@ pub enum PathType {
} }
use self::PathType::*; use self::PathType::*;
pub struct Mesher { pub struct MesherOptions {
pub anti_alias: bool, pub anti_alias: bool,
pub aa_size: f32, pub aa_size: f32,
/// Where the output goes
pub mesh: Mesh,
} }
impl Mesher { pub fn fill_closed_path(
pub fn new(pixels_per_point: f32) -> Mesher { mesh: &mut Mesh,
Mesher { options: &MesherOptions,
anti_alias: true, path: &[PathPoint],
aa_size: 1.0 / pixels_per_point, color: Color,
mesh: Default::default(), ) {
} let n = path.len() as u32;
}
pub fn fill_closed_path(&mut self, points: &[Vec2], normals: &[Vec2], color: Color) {
assert_eq!(points.len(), normals.len());
let n = points.len() as u32;
let vert = |pos, color| Vertex { let vert = |pos, color| Vertex {
pos, pos,
uv: WHITE_UV, uv: WHITE_UV,
color, color,
}; };
let mesh = &mut self.mesh; if options.anti_alias {
if self.anti_alias {
let color_outer = color.transparent(); let color_outer = color.transparent();
let idx_inner = mesh.vertices.len() as u32; let idx_inner = mesh.vertices.len() as u32;
let idx_outer = idx_inner + 1; let idx_outer = idx_inner + 1;
@ -161,10 +252,10 @@ impl Mesher {
} }
let mut i0 = n - 1; let mut i0 = n - 1;
for i1 in 0..n { for i1 in 0..n {
let dm = normals[i1 as usize] * self.aa_size * 0.5; let p1 = &path[i1 as usize];
mesh.vertices.push(vert(points[i1 as usize] - dm, color)); let dm = p1.normal * options.aa_size * 0.5;
mesh.vertices mesh.vertices.push(vert(p1.pos - dm, color));
.push(vert(points[i1 as usize] + dm, color_outer)); mesh.vertices.push(vert(p1.pos + dm, color_outer));
mesh.triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0); mesh.triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
mesh.triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1); mesh.triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
i0 = i1; i0 = i1;
@ -172,7 +263,7 @@ impl Mesher {
} else { } else {
let idx = mesh.vertices.len() as u32; let idx = mesh.vertices.len() as u32;
mesh.vertices mesh.vertices
.extend(points.iter().map(|&pos| vert(pos, color))); .extend(path.iter().map(|p| vert(p.pos, color)));
for i in 2..n { for i in 2..n {
mesh.triangle(idx, idx + i - 1, idx + i); mesh.triangle(idx, idx + i - 1, idx + i);
} }
@ -180,26 +271,24 @@ impl Mesher {
} }
pub fn paint_path( pub fn paint_path(
&mut self, mesh: &mut Mesh,
options: &MesherOptions,
path_type: PathType, path_type: PathType,
points: &[Vec2], path: &[PathPoint],
normals: &[Vec2],
color: Color, color: Color,
width: f32, width: f32,
) { ) {
assert_eq!(points.len(), normals.len()); let n = path.len() as u32;
let n = points.len() as u32;
let hw = width / 2.0; let hw = width / 2.0;
let idx = self.mesh.vertices.len() as u32; let idx = mesh.vertices.len() as u32;
let vert = |pos, color| Vertex { let vert = |pos, color| Vertex {
pos, pos,
uv: WHITE_UV, uv: WHITE_UV,
color, color,
}; };
let mesh = &mut self.mesh;
if self.anti_alias { if options.anti_alias {
let color_outer = color.transparent(); let color_outer = color.transparent();
let thin_line = width <= 1.0; let thin_line = width <= 1.0;
let mut color_inner = color; let mut color_inner = color;
@ -212,11 +301,14 @@ impl Mesher {
for i1 in 0..n { for i1 in 0..n {
let connect_with_previous = path_type == PathType::Closed || i1 > 0; let connect_with_previous = path_type == PathType::Closed || i1 > 0;
if thin_line { if thin_line {
let p = points[i1 as usize]; let p1 = &path[i1 as usize];
let n = normals[i1 as usize]; let p = p1.pos;
mesh.vertices.push(vert(p + n * self.aa_size, color_outer)); let n = p1.normal;
mesh.vertices
.push(vert(p + n * options.aa_size, color_outer));
mesh.vertices.push(vert(p, color_inner)); mesh.vertices.push(vert(p, color_inner));
mesh.vertices.push(vert(p - n * self.aa_size, color_outer)); mesh.vertices
.push(vert(p - n * options.aa_size, color_outer));
if connect_with_previous { if connect_with_previous {
mesh.triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0); mesh.triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
@ -226,15 +318,16 @@ impl Mesher {
mesh.triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2); mesh.triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
} }
} else { } else {
let hw = (width - self.aa_size) * 0.5; let hw = (width - options.aa_size) * 0.5;
let p = points[i1 as usize]; let p1 = &path[i1 as usize];
let n = normals[i1 as usize]; let p = p1.pos;
let n = p1.normal;
mesh.vertices mesh.vertices
.push(vert(p + n * (hw + self.aa_size), color_outer)); .push(vert(p + n * (hw + options.aa_size), color_outer));
mesh.vertices.push(vert(p + n * (hw + 0.0), color_inner)); mesh.vertices.push(vert(p + n * (hw + 0.0), color_inner));
mesh.vertices.push(vert(p - n * (hw + 0.0), color_inner)); mesh.vertices.push(vert(p - n * (hw + 0.0), color_inner));
mesh.vertices mesh.vertices
.push(vert(p - n * (hw + self.aa_size), color_outer)); .push(vert(p - n * (hw + options.aa_size), color_outer));
if connect_with_previous { if connect_with_previous {
mesh.triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); mesh.triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
@ -264,16 +357,35 @@ impl Mesher {
); );
} }
for (&p, &n) in points.iter().zip(normals) { for p in path {
mesh.vertices.push(vert(p + hw * n, color)); mesh.vertices.push(vert(p.pos + hw * p.normal, color));
mesh.vertices.push(vert(p - hw * n, color)); mesh.vertices.push(vert(p.pos - hw * p.normal, color));
} }
} }
} }
// ----------------------------------------------------------------------------
pub struct Mesher {
pub options: MesherOptions,
/// Where the output goes
pub mesh: Mesh,
}
impl Mesher {
pub fn new(pixels_per_point: f32) -> Mesher {
Mesher {
options: MesherOptions {
anti_alias: true,
aa_size: 1.0 / pixels_per_point,
},
mesh: Default::default(),
}
}
pub fn paint(&mut self, fonts: &Fonts, commands: &[PaintCmd]) { pub fn paint(&mut self, fonts: &Fonts, commands: &[PaintCmd]) {
let mut path_points = Vec::new(); let mut path = Path::default();
let mut path_normals = Vec::new();
for cmd in commands { for cmd in commands {
match cmd { match cmd {
@ -283,25 +395,17 @@ impl Mesher {
outline, outline,
radius, radius,
} => { } => {
path_points.clear(); path.clear();
path_normals.clear(); path.add_circle(*center, *radius);
let n = 32; // TODO: parameter
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());
path_normals.push(normal);
path_points.push(*center + *radius * normal);
}
if let Some(color) = fill_color { if let Some(color) = fill_color {
self.fill_closed_path(&path_points, &path_normals, *color); fill_closed_path(&mut self.mesh, &self.options, &path.0, *color);
} }
if let Some(outline) = outline { if let Some(outline) = outline {
self.paint_path( paint_path(
&mut self.mesh,
&self.options,
Closed, Closed,
&path_points, &path.0,
&path_normals,
outline.color, outline.color,
outline.width, outline.width,
); );
@ -317,24 +421,9 @@ impl Mesher {
} => { } => {
let n = points.len(); let n = points.len();
if n >= 2 { if n >= 2 {
path_points = points.clone(); path.clear();
path_normals.clear(); path.add_line(points);
paint_path(&mut self.mesh, &self.options, Open, &path.0, *color, *width);
path_normals.push((path_points[1] - path_points[0]).normalized().rot90());
for i in 1..n - 1 {
let n0 = (path_points[i] - path_points[i - 1]).normalized().rot90();
let n1 = (path_points[i + 1] - path_points[i]).normalized().rot90();
let v = (n0 + n1) / 2.0;
let normal = v / v.length_sq();
path_normals.push(normal); // TODO: handle VERY sharp turns better
}
path_normals.push(
(path_points[n - 1] - path_points[n - 2])
.normalized()
.rot90(),
);
self.paint_path(Open, &path_points, &path_normals, *color, *width);
} }
} }
PaintCmd::Rect { PaintCmd::Rect {
@ -343,60 +432,17 @@ impl Mesher {
outline, outline,
rect, rect,
} => { } => {
path_points.clear(); path.clear();
path_normals.clear(); path.add_rounded_rectangle(rect, *corner_radius);
let min = rect.min();
let max = rect.max();
let cr = corner_radius
.min(rect.width() * 0.5)
.min(rect.height() * 0.5);
if cr <= 0.0 {
path_points.push(vec2(min.x, min.y));
path_normals.push(vec2(-1.0, -1.0));
path_points.push(vec2(max.x, min.y));
path_normals.push(vec2(1.0, -1.0));
path_points.push(vec2(max.x, max.y));
path_normals.push(vec2(1.0, 1.0));
path_points.push(vec2(min.x, max.y));
path_normals.push(vec2(-1.0, 1.0));
} else {
let n = 8;
let mut add_arc = |c, quadrant| {
let quadrant = quadrant as f32;
const RIGHT_ANGLE: f32 = TAU / 4.0;
for i in 0..=n {
let angle = remap(
i as f32,
0.0,
n as f32,
quadrant * RIGHT_ANGLE,
(quadrant + 1.0) * RIGHT_ANGLE,
);
let normal = vec2(angle.cos(), angle.sin());
path_points.push(c + cr * normal);
path_normals.push(normal);
}
};
add_arc(vec2(max.x - cr, max.y - cr), 0);
add_arc(vec2(min.x + cr, max.y - cr), 1);
add_arc(vec2(min.x + cr, min.y + cr), 2);
add_arc(vec2(max.x - cr, min.y + cr), 3);
}
if let Some(fill_color) = fill_color { if let Some(fill_color) = fill_color {
self.fill_closed_path(&path_points, &path_normals, *fill_color); fill_closed_path(&mut self.mesh, &self.options, &path.0, *fill_color);
} }
if let Some(outline) = outline { if let Some(outline) = outline {
self.paint_path( paint_path(
&mut self.mesh,
&self.options,
Closed, Closed,
&path_points, &path.0,
&path_normals,
outline.color, outline.color,
outline.width, outline.width,
); );