From 734d4c57adb8f69308fb8ba609e33a6eceeda53c Mon Sep 17 00:00:00 2001 From: jean-airoldie <25088801+jean-airoldie@users.noreply.github.com> Date: Sun, 20 Mar 2022 15:38:48 -0400 Subject: [PATCH] Expose more epaint tessellator methods (#1384) * Expose more tessellator method * Make public the Tessellator methods to tessellate a circle, a mesh, a rectangle, a line, a path, a quadratic and cubic bezier curve. * Add doc to tessellate_text. * Add Mesh::append_ref method. * Make tessellate_text take a reference * Fix breaking change in benchmark --- egui_demo_lib/benches/benchmark.rs | 2 +- epaint/src/mesh.rs | 22 +- epaint/src/tessellator.rs | 486 ++++++++++++++++------------- 3 files changed, 291 insertions(+), 219 deletions(-) diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index 4fdeb730..9b988b3f 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -129,7 +129,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let font_image_size = fonts.font_image_size(); c.bench_function("tessellate_text", |b| { b.iter(|| { - tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh); + tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh); mesh.clear(); }) }); diff --git a/epaint/src/mesh.rs b/epaint/src/mesh.rs index 800e6120..7a5e0dcd 100644 --- a/epaint/src/mesh.rs +++ b/epaint/src/mesh.rs @@ -91,16 +91,28 @@ impl Mesh { if self.is_empty() { *self = other; } else { + self.append_ref(&other); + } + } + + /// Append all the indices and vertices of `other` to `self` without + /// taking ownership. + pub fn append_ref(&mut self, other: &Mesh) { + crate::epaint_assert!(other.is_valid()); + + if !self.is_empty() { assert_eq!( self.texture_id, other.texture_id, "Can't merge Mesh using different textures" ); - - let index_offset = self.vertices.len() as u32; - self.indices - .extend(other.indices.iter().map(|index| index + index_offset)); - self.vertices.extend(other.vertices.iter()); + } else { + self.texture_id = other.texture_id; } + + let index_offset = self.vertices.len() as u32; + self.indices + .extend(other.indices.iter().map(|index| index + index_offset)); + self.vertices.extend(other.vertices.iter()); } #[inline(always)] diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 95fde7f8..440f356e 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -706,9 +706,6 @@ impl Tessellator { /// * `shape`: the shape to tessellate. /// * `out`: triangles are appended to this. pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) { - let clip_rect = self.clip_rect; - let options = &self.options; - match shape { Shape::Noop => {} Shape::Vec(vec) => { @@ -716,26 +713,8 @@ impl Tessellator { self.tessellate_shape(tex_size, shape, out); } } - Shape::Circle(CircleShape { - center, - radius, - fill, - stroke, - }) => { - if radius <= 0.0 { - return; - } - - if options.coarse_tessellation_culling - && !clip_rect.expand(radius + stroke.width).contains(center) - { - return; - } - - self.scratchpad_path.clear(); - self.scratchpad_path.add_circle(center, radius); - self.scratchpad_path.fill(fill, options, out); - self.scratchpad_path.stroke_closed(stroke, options, out); + Shape::Circle(circle) => { + self.tessellate_circle(circle, out); } Shape::Mesh(mesh) => { if !mesh.is_valid() { @@ -743,44 +722,29 @@ impl Tessellator { return; } - if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds()) + if self.options.coarse_tessellation_culling + && !self.clip_rect.intersects(mesh.calc_bounds()) { return; } - out.append(mesh); } - Shape::LineSegment { points, stroke } => { - if stroke.is_empty() { - return; - } - - if options.coarse_tessellation_culling - && !clip_rect - .intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width)) - { - return; - } - - self.scratchpad_path.clear(); - self.scratchpad_path.add_line_segment(points); - self.scratchpad_path.stroke_open(stroke, options, out); - } + Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out), Shape::Path(path_shape) => { - self.tessellate_path(path_shape, out); + self.tessellate_path(&path_shape, out); } Shape::Rect(rect_shape) => { self.tessellate_rect(&rect_shape, out); } Shape::Text(text_shape) => { - if options.debug_paint_text_rects { + if self.options.debug_paint_text_rects { let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2()); self.tessellate_rect( &RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)), out, ); } - self.tessellate_text(tex_size, text_shape, out); + self.tessellate_text(tex_size, &text_shape, out); } Shape::QuadraticBezier(quadratic_shape) => { self.tessellate_quadratic_bezier(quadratic_shape, out); @@ -792,7 +756,266 @@ impl Tessellator { } } - pub(crate) fn tessellate_quadratic_bezier( + /// Tessellate a single [`CircleShape`] into a [`Mesh`]. + /// + /// * `shape`: the circle to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) { + let CircleShape { + center, + radius, + fill, + stroke, + } = shape; + + if radius <= 0.0 { + return; + } + + if self.options.coarse_tessellation_culling + && !self + .clip_rect + .expand(radius + stroke.width) + .contains(center) + { + return; + } + + self.scratchpad_path.clear(); + self.scratchpad_path.add_circle(center, radius); + self.scratchpad_path.fill(fill, &self.options, out); + self.scratchpad_path + .stroke_closed(stroke, &self.options, out); + } + + /// Tessellate a single [`Mesh`] into a [`Mesh`]. + /// + /// * `mesh`: the mesh to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) { + if !mesh.is_valid() { + crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + return; + } + + if self.options.coarse_tessellation_culling + && !self.clip_rect.intersects(mesh.calc_bounds()) + { + return; + } + + out.append_ref(mesh); + } + + /// Tessellate a line segment between the two points with the given stoken into a [`Mesh`]. + /// + /// * `shape`: the mesh to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) { + if stroke.is_empty() { + return; + } + + if self.options.coarse_tessellation_culling + && !self + .clip_rect + .intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width)) + { + return; + } + + self.scratchpad_path.clear(); + self.scratchpad_path.add_line_segment(points); + self.scratchpad_path.stroke_open(stroke, &self.options, out); + } + + /// Tessellate a single [`PathShape`] into a [`Mesh`]. + /// + /// * `path_shape`: the path to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) { + if path_shape.points.len() < 2 { + return; + } + + if self.options.coarse_tessellation_culling + && !path_shape.visual_bounding_rect().intersects(self.clip_rect) + { + return; + } + + let PathShape { + points, + closed, + fill, + stroke, + } = path_shape; + + self.scratchpad_path.clear(); + if *closed { + self.scratchpad_path.add_line_loop(points); + } else { + self.scratchpad_path.add_open_points(points); + } + + if *fill != Color32::TRANSPARENT { + crate::epaint_assert!( + closed, + "You asked to fill a path that is not closed. That makes no sense." + ); + self.scratchpad_path.fill(*fill, &self.options, out); + } + let typ = if *closed { + PathType::Closed + } else { + PathType::Open + }; + self.scratchpad_path + .stroke(typ, *stroke, &self.options, out); + } + + /// Tessellate a single [`Rect`] into a [`Mesh`]. + /// + /// * `rect`: the rectangle to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { + let RectShape { + mut rect, + rounding, + fill, + stroke, + } = *rect; + + if self.options.coarse_tessellation_culling + && !rect.expand(stroke.width).intersects(self.clip_rect) + { + return; + } + if rect.is_negative() { + return; + } + + // It is common to (sometimes accidentally) create an infinitely sized rectangle. + // Make sure we can handle that: + rect.min = rect.min.at_least(pos2(-1e7, -1e7)); + rect.max = rect.max.at_most(pos2(1e7, 1e7)); + + let path = &mut self.scratchpad_path; + path.clear(); + path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); + path.add_line_loop(&self.scratchpad_points); + path.fill(fill, &self.options, out); + path.stroke_closed(stroke, &self.options, out); + } + + /// Tessellate a single [`TextShape`] into a [`Mesh`]. + /// + /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). + /// * `text_shape`: the text to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_text( + &mut self, + tex_size: [usize; 2], + text_shape: &TextShape, + out: &mut Mesh, + ) { + let TextShape { + pos: galley_pos, + galley, + underline, + override_text_color, + angle, + } = text_shape; + + if galley.is_empty() { + return; + } + + out.vertices.reserve(galley.num_vertices); + out.indices.reserve(galley.num_indices); + + // The contents of the galley is already snapped to pixel coordinates, + // but we need to make sure the galley ends up on the start of a physical pixel: + let galley_pos = pos2( + self.options.round_to_pixel(galley_pos.x), + self.options.round_to_pixel(galley_pos.y), + ); + + let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); + + let rotator = Rot2::from_angle(*angle); + + for row in &galley.rows { + if row.visuals.mesh.is_empty() { + continue; + } + + let mut row_rect = row.visuals.mesh_bounds; + if *angle != 0.0 { + row_rect = row_rect.rotate_bb(rotator); + } + row_rect = row_rect.translate(galley_pos.to_vec2()); + + if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { + // culling individual lines of text is important, since a single `Shape::Text` + // can span hundreds of lines. + continue; + } + + let index_offset = out.vertices.len() as u32; + + out.indices.extend( + row.visuals + .mesh + .indices + .iter() + .map(|index| index + index_offset), + ); + + out.vertices.extend( + row.visuals + .mesh + .vertices + .iter() + .enumerate() + .map(|(i, vertex)| { + let Vertex { pos, uv, mut color } = *vertex; + + if let Some(override_text_color) = override_text_color { + if row.visuals.glyph_vertex_range.contains(&i) { + color = *override_text_color; + } + } + + let offset = if *angle == 0.0 { + pos.to_vec2() + } else { + rotator * pos.to_vec2() + }; + + Vertex { + pos: galley_pos + offset, + uv: (uv.to_vec2() * uv_normalizer).to_pos2(), + color, + } + }), + ); + + if *underline != Stroke::none() { + self.scratchpad_path.clear(); + self.scratchpad_path + .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); + self.scratchpad_path + .stroke_open(*underline, &self.options, out); + } + } + } + + /// Tessellate a single [`QuadraticBezierShape`] into a [`Mesh`]. + /// + /// * `quadratic_shape`: the shape to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_quadratic_bezier( &mut self, quadratic_shape: QuadraticBezierShape, out: &mut Mesh, @@ -817,11 +1040,11 @@ impl Tessellator { ); } - pub(crate) fn tessellate_cubic_bezier( - &mut self, - cubic_shape: CubicBezierShape, - out: &mut Mesh, - ) { + /// Tessellate a single [`CubicBezierShape`] into a [`Mesh`]. + /// + /// * `cubic_shape`: the shape to tessellate. + /// * `out`: triangles are appended to this. + pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) { let options = &self.options; let clip_rect = self.clip_rect; if options.coarse_tessellation_culling @@ -872,169 +1095,6 @@ impl Tessellator { }; self.scratchpad_path.stroke(typ, stroke, &self.options, out); } - - pub(crate) fn tessellate_path(&mut self, path_shape: PathShape, out: &mut Mesh) { - if path_shape.points.len() < 2 { - return; - } - - if self.options.coarse_tessellation_culling - && !path_shape.visual_bounding_rect().intersects(self.clip_rect) - { - return; - } - - let PathShape { - points, - closed, - fill, - stroke, - } = path_shape; - - self.scratchpad_path.clear(); - if closed { - self.scratchpad_path.add_line_loop(&points); - } else { - self.scratchpad_path.add_open_points(&points); - } - - if fill != Color32::TRANSPARENT { - crate::epaint_assert!( - closed, - "You asked to fill a path that is not closed. That makes no sense." - ); - self.scratchpad_path.fill(fill, &self.options, out); - } - let typ = if closed { - PathType::Closed - } else { - PathType::Open - }; - self.scratchpad_path.stroke(typ, stroke, &self.options, out); - } - - pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { - let RectShape { - mut rect, - rounding, - fill, - stroke, - } = *rect; - - if self.options.coarse_tessellation_culling - && !rect.expand(stroke.width).intersects(self.clip_rect) - { - return; - } - if rect.is_negative() { - return; - } - - // It is common to (sometimes accidentally) create an infinitely sized rectangle. - // Make sure we can handle that: - rect.min = rect.min.at_least(pos2(-1e7, -1e7)); - rect.max = rect.max.at_most(pos2(1e7, 1e7)); - - let path = &mut self.scratchpad_path; - path.clear(); - path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); - path.add_line_loop(&self.scratchpad_points); - path.fill(fill, &self.options, out); - path.stroke_closed(stroke, &self.options, out); - } - - pub fn tessellate_text(&mut self, tex_size: [usize; 2], text_shape: TextShape, out: &mut Mesh) { - let TextShape { - pos: galley_pos, - galley, - underline, - override_text_color, - angle, - } = text_shape; - - if galley.is_empty() { - return; - } - - out.vertices.reserve(galley.num_vertices); - out.indices.reserve(galley.num_indices); - - // The contents of the galley is already snapped to pixel coordinates, - // but we need to make sure the galley ends up on the start of a physical pixel: - let galley_pos = pos2( - self.options.round_to_pixel(galley_pos.x), - self.options.round_to_pixel(galley_pos.y), - ); - - let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32); - - let rotator = Rot2::from_angle(angle); - - for row in &galley.rows { - if row.visuals.mesh.is_empty() { - continue; - } - - let mut row_rect = row.visuals.mesh_bounds; - if angle != 0.0 { - row_rect = row_rect.rotate_bb(rotator); - } - row_rect = row_rect.translate(galley_pos.to_vec2()); - - if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) { - // culling individual lines of text is important, since a single `Shape::Text` - // can span hundreds of lines. - continue; - } - - let index_offset = out.vertices.len() as u32; - - out.indices.extend( - row.visuals - .mesh - .indices - .iter() - .map(|index| index + index_offset), - ); - - out.vertices.extend( - row.visuals - .mesh - .vertices - .iter() - .enumerate() - .map(|(i, vertex)| { - let Vertex { pos, uv, mut color } = *vertex; - - if let Some(override_text_color) = override_text_color { - if row.visuals.glyph_vertex_range.contains(&i) { - color = override_text_color; - } - } - - let offset = if angle == 0.0 { - pos.to_vec2() - } else { - rotator * pos.to_vec2() - }; - - Vertex { - pos: galley_pos + offset, - uv: (uv.to_vec2() * uv_normalizer).to_pos2(), - color, - } - }), - ); - - if underline != Stroke::none() { - self.scratchpad_path.clear(); - self.scratchpad_path - .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); - self.scratchpad_path - .stroke_open(underline, &self.options, out); - } - } - } } /// Turns [`Shape`]:s into sets of triangles.