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
This commit is contained in:
parent
8bb381d50b
commit
734d4c57ad
3 changed files with 291 additions and 219 deletions
|
@ -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();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue