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 {
let paint_commands = self.ctx.drain_paint_lists();
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);
let mesh = mesher.mesh;

View file

@ -4,7 +4,7 @@
use crate::{
color::Color,
fonts::Fonts,
math::{remap, vec2, Vec2, TAU},
math::{remap, vec2, Rect, Vec2, TAU},
types::PaintCmd,
};
@ -20,6 +20,8 @@ pub struct Vertex {
pub color: Color,
}
// ----------------------------------------------------------------------------
#[derive(Clone, Debug, Default, Serialize)]
pub struct Mesh {
/// 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)]
pub enum PathType {
Open,
@ -126,33 +226,24 @@ pub enum PathType {
}
use self::PathType::*;
pub struct Mesher {
pub struct MesherOptions {
pub anti_alias: bool,
pub aa_size: f32,
/// Where the output goes
pub mesh: Mesh,
}
impl Mesher {
pub fn new(pixels_per_point: f32) -> Mesher {
Mesher {
anti_alias: true,
aa_size: 1.0 / pixels_per_point,
mesh: Default::default(),
}
}
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;
pub fn fill_closed_path(
mesh: &mut Mesh,
options: &MesherOptions,
path: &[PathPoint],
color: Color,
) {
let n = path.len() as u32;
let vert = |pos, color| Vertex {
pos,
uv: WHITE_UV,
color,
};
let mesh = &mut self.mesh;
if self.anti_alias {
if options.anti_alias {
let color_outer = color.transparent();
let idx_inner = mesh.vertices.len() as u32;
let idx_outer = idx_inner + 1;
@ -161,10 +252,10 @@ impl Mesher {
}
let mut i0 = n - 1;
for i1 in 0..n {
let dm = normals[i1 as usize] * self.aa_size * 0.5;
mesh.vertices.push(vert(points[i1 as usize] - dm, color));
mesh.vertices
.push(vert(points[i1 as usize] + dm, color_outer));
let p1 = &path[i1 as usize];
let dm = p1.normal * options.aa_size * 0.5;
mesh.vertices.push(vert(p1.pos - dm, color));
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_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
i0 = i1;
@ -172,7 +263,7 @@ impl Mesher {
} else {
let idx = mesh.vertices.len() as u32;
mesh.vertices
.extend(points.iter().map(|&pos| vert(pos, color)));
.extend(path.iter().map(|p| vert(p.pos, color)));
for i in 2..n {
mesh.triangle(idx, idx + i - 1, idx + i);
}
@ -180,26 +271,24 @@ impl Mesher {
}
pub fn paint_path(
&mut self,
mesh: &mut Mesh,
options: &MesherOptions,
path_type: PathType,
points: &[Vec2],
normals: &[Vec2],
path: &[PathPoint],
color: Color,
width: f32,
) {
assert_eq!(points.len(), normals.len());
let n = points.len() as u32;
let n = path.len() as u32;
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 {
pos,
uv: WHITE_UV,
color,
};
let mesh = &mut self.mesh;
if self.anti_alias {
if options.anti_alias {
let color_outer = color.transparent();
let thin_line = width <= 1.0;
let mut color_inner = color;
@ -212,11 +301,14 @@ impl Mesher {
for i1 in 0..n {
let connect_with_previous = path_type == PathType::Closed || i1 > 0;
if thin_line {
let p = points[i1 as usize];
let n = normals[i1 as usize];
mesh.vertices.push(vert(p + n * self.aa_size, color_outer));
let p1 = &path[i1 as usize];
let p = p1.pos;
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 - n * self.aa_size, color_outer));
mesh.vertices
.push(vert(p - n * options.aa_size, color_outer));
if connect_with_previous {
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);
}
} else {
let hw = (width - self.aa_size) * 0.5;
let p = points[i1 as usize];
let n = normals[i1 as usize];
let hw = (width - options.aa_size) * 0.5;
let p1 = &path[i1 as usize];
let p = p1.pos;
let n = p1.normal;
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 + self.aa_size), color_outer));
.push(vert(p - n * (hw + options.aa_size), color_outer));
if connect_with_previous {
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) {
mesh.vertices.push(vert(p + hw * n, color));
mesh.vertices.push(vert(p - hw * n, color));
for p in path {
mesh.vertices.push(vert(p.pos + hw * p.normal, 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]) {
let mut path_points = Vec::new();
let mut path_normals = Vec::new();
let mut path = Path::default();
for cmd in commands {
match cmd {
@ -283,25 +395,17 @@ impl Mesher {
outline,
radius,
} => {
path_points.clear();
path_normals.clear();
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);
}
path.clear();
path.add_circle(*center, *radius);
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 {
self.paint_path(
paint_path(
&mut self.mesh,
&self.options,
Closed,
&path_points,
&path_normals,
&path.0,
outline.color,
outline.width,
);
@ -317,24 +421,9 @@ impl Mesher {
} => {
let n = points.len();
if n >= 2 {
path_points = points.clone();
path_normals.clear();
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);
path.clear();
path.add_line(points);
paint_path(&mut self.mesh, &self.options, Open, &path.0, *color, *width);
}
}
PaintCmd::Rect {
@ -343,60 +432,17 @@ impl Mesher {
outline,
rect,
} => {
path_points.clear();
path_normals.clear();
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);
}
path.clear();
path.add_rounded_rectangle(rect, *corner_radius);
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 {
self.paint_path(
paint_path(
&mut self.mesh,
&self.options,
Closed,
&path_points,
&path_normals,
&path.0,
outline.color,
outline.width,
);