use crate::{
FillRule, LineCap, LineJoin, Path, PathCommand, Shape, Stroke,
vertex::{FillVertex, StrokeVertex, TessellatedMesh},
};
use glam::Vec2;
#[allow(unused_imports)]
use lyon::geom as lyon_geom;
use lyon::geom::{Arc, ArcFlags, SvgArc};
use lyon::lyon_tessellation::{
BuffersBuilder, FillOptions, FillTessellator, FillVertex as LyonFillVertex, StrokeOptions,
StrokeTessellator, StrokeVertex as LyonStrokeVertex, VertexBuffers,
};
use lyon::math::{Angle, Point, Vector};
use lyon::path::PathEvent;
pub struct Tessellator {
fill_tessellator: FillTessellator,
stroke_tessellator: StrokeTessellator,
pub tolerance: f32,
}
impl Default for Tessellator {
fn default() -> Self {
Self::new()
}
}
impl Tessellator {
pub fn new() -> Self {
Self {
fill_tessellator: FillTessellator::new(),
stroke_tessellator: StrokeTessellator::new(),
tolerance: 0.5,
}
}
pub fn with_tolerance(tolerance: f32) -> Self {
Self {
fill_tessellator: FillTessellator::new(),
stroke_tessellator: StrokeTessellator::new(),
tolerance,
}
}
pub fn tessellate_fill(
&mut self,
path: &Path,
fill_rule: FillRule,
) -> TessellatedMesh<FillVertex> {
let mut buffers: VertexBuffers<FillVertex, u32> = VertexBuffers::new();
let options = FillOptions::default()
.with_tolerance(self.tolerance)
.with_fill_rule(convert_fill_rule(fill_rule));
let events = path_to_events(path);
let result = self.fill_tessellator.tessellate(
events,
&options,
&mut BuffersBuilder::new(&mut buffers, |vertex: LyonFillVertex| {
FillVertex::new(vertex.position().x, vertex.position().y)
}),
);
if result.is_err() {
tracing::warn!("Fill tessellation failed");
return TessellatedMesh::new();
}
TessellatedMesh::from_data(buffers.vertices, buffers.indices)
}
pub fn tessellate_stroke(
&mut self,
path: &Path,
stroke: &Stroke,
) -> TessellatedMesh<StrokeVertex> {
let mut buffers: VertexBuffers<StrokeVertex, u32> = VertexBuffers::new();
let options = StrokeOptions::default()
.with_tolerance(self.tolerance)
.with_line_width(stroke.width)
.with_line_cap(convert_line_cap(stroke.line_cap))
.with_line_join(convert_line_join(stroke.line_join))
.with_miter_limit(stroke.miter_limit);
let events = path_to_events(path);
let result = self.stroke_tessellator.tessellate(
events,
&options,
&mut BuffersBuilder::new(&mut buffers, |vertex: LyonStrokeVertex| {
StrokeVertex::new(
vertex.position().x,
vertex.position().y,
vertex.normal().x,
vertex.normal().y,
vertex.advancement(),
match vertex.side() {
lyon::lyon_tessellation::Side::Negative => -1.0,
lyon::lyon_tessellation::Side::Positive => 1.0,
},
)
}),
);
if result.is_err() {
tracing::warn!("Stroke tessellation failed");
return TessellatedMesh::new();
}
TessellatedMesh::from_data(buffers.vertices, buffers.indices)
}
pub fn tessellate_shape_fill(
&mut self,
shape: &Shape,
fill_rule: FillRule,
) -> TessellatedMesh<FillVertex> {
self.tessellate_fill(&shape.to_path(), fill_rule)
}
pub fn tessellate_shape_stroke(
&mut self,
shape: &Shape,
stroke: &Stroke,
) -> TessellatedMesh<StrokeVertex> {
self.tessellate_stroke(&shape.to_path(), stroke)
}
pub fn tessellate_rect_fill(&self, position: Vec2, size: Vec2) -> TessellatedMesh<FillVertex> {
let vertices = vec![
FillVertex::new(position.x, position.y),
FillVertex::new(position.x + size.x, position.y),
FillVertex::new(position.x + size.x, position.y + size.y),
FillVertex::new(position.x, position.y + size.y),
];
let indices = vec![0, 1, 2, 0, 2, 3];
TessellatedMesh::from_data(vertices, indices)
}
pub fn tessellate_line(
&self,
start: Vec2,
end: Vec2,
width: f32,
) -> TessellatedMesh<FillVertex> {
let dir = (end - start).normalize_or_zero();
let normal = Vec2::new(-dir.y, dir.x) * (width * 0.5);
let vertices = vec![
FillVertex::new(start.x - normal.x, start.y - normal.y),
FillVertex::new(start.x + normal.x, start.y + normal.y),
FillVertex::new(end.x + normal.x, end.y + normal.y),
FillVertex::new(end.x - normal.x, end.y - normal.y),
];
let indices = vec![0, 1, 2, 0, 2, 3];
TessellatedMesh::from_data(vertices, indices)
}
}
fn path_to_events(path: &Path) -> Vec<PathEvent> {
let mut events = Vec::new();
let mut current = lyon::math::point(0.0, 0.0);
let mut subpath_start = current;
let mut in_subpath = false;
for cmd in path.commands() {
match cmd {
PathCommand::MoveTo(to) => {
if in_subpath {
events.push(PathEvent::End {
last: current,
first: subpath_start,
close: false,
});
}
current = lyon::math::point(to.x, to.y);
subpath_start = current;
events.push(PathEvent::Begin { at: current });
in_subpath = true;
}
PathCommand::LineTo(to) => {
let from = current;
current = lyon::math::point(to.x, to.y);
events.push(PathEvent::Line { from, to: current });
}
PathCommand::QuadTo { control, to } => {
let from = current;
let ctrl = lyon::math::point(control.x, control.y);
current = lyon::math::point(to.x, to.y);
events.push(PathEvent::Quadratic {
from,
ctrl,
to: current,
});
}
PathCommand::CubicTo {
control1,
control2,
to,
} => {
let from = current;
let ctrl1 = lyon::math::point(control1.x, control1.y);
let ctrl2 = lyon::math::point(control2.x, control2.y);
current = lyon::math::point(to.x, to.y);
events.push(PathEvent::Cubic {
from,
ctrl1,
ctrl2,
to: current,
});
}
PathCommand::ArcTo {
radii,
x_rotation,
large_arc,
sweep,
to,
} => {
let from_point = Point::new(current.x, current.y);
let to_point = Point::new(to.x, to.y);
let radii_vec = Vector::new(radii.x, radii.y);
let svg_arc = SvgArc {
from: from_point,
to: to_point,
radii: radii_vec,
x_rotation: Angle::radians(*x_rotation),
flags: ArcFlags {
large_arc: *large_arc,
sweep: *sweep,
},
};
let arc = svg_arc.to_arc();
if arc.sweep_angle.radians.abs() < 0.001 {
let from = current;
current = lyon::math::point(to.x, to.y);
events.push(PathEvent::Line { from, to: current });
} else {
let (ctrl1, ctrl2) = arc_to_cubic_control_points(&arc);
let from = current;
current = lyon::math::point(to.x, to.y);
events.push(PathEvent::Cubic {
from,
ctrl1: lyon::math::point(ctrl1.x, ctrl1.y),
ctrl2: lyon::math::point(ctrl2.x, ctrl2.y),
to: current,
});
}
}
PathCommand::Close => {
let last = current;
current = subpath_start;
events.push(PathEvent::End {
last,
first: subpath_start,
close: true,
});
in_subpath = false;
}
}
}
if in_subpath {
events.push(PathEvent::End {
last: current,
first: subpath_start,
close: false,
});
}
events
}
fn arc_to_cubic_control_points(arc: &Arc<f32>) -> (Vec2, Vec2) {
let sweep_angle = arc.sweep_angle.radians.abs();
let sweep_angle = sweep_angle.min(std::f32::consts::FRAC_PI_2 * 0.99);
let k = (4.0 / 3.0) * (sweep_angle / 4.0).tan();
let start = arc.from();
let end = arc.to();
let start_tangent = arc.sample_tangent(0.0);
let end_tangent = arc.sample_tangent(1.0);
let ctrl1 = Vec2::new(
start.x + k * start_tangent.x * arc.radii.x,
start.y + k * start_tangent.y * arc.radii.y,
);
let ctrl2 = Vec2::new(
end.x - k * end_tangent.x * arc.radii.x,
end.y - k * end_tangent.y * arc.radii.y,
);
(ctrl1, ctrl2)
}
fn convert_fill_rule(rule: FillRule) -> lyon::lyon_tessellation::FillRule {
match rule {
FillRule::NonZero => lyon::lyon_tessellation::FillRule::NonZero,
FillRule::EvenOdd => lyon::lyon_tessellation::FillRule::EvenOdd,
}
}
fn convert_line_cap(cap: LineCap) -> lyon::lyon_tessellation::LineCap {
match cap {
LineCap::Butt => lyon::lyon_tessellation::LineCap::Butt,
LineCap::Round => lyon::lyon_tessellation::LineCap::Round,
LineCap::Square => lyon::lyon_tessellation::LineCap::Square,
}
}
fn convert_line_join(join: LineJoin) -> lyon::lyon_tessellation::LineJoin {
match join {
LineJoin::Miter => lyon::lyon_tessellation::LineJoin::Miter,
LineJoin::Round => lyon::lyon_tessellation::LineJoin::Round,
LineJoin::Bevel => lyon::lyon_tessellation::LineJoin::Bevel,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PathBuilder;
#[test]
fn test_rect_tessellation() {
let tessellator = Tessellator::new();
let mesh = tessellator.tessellate_rect_fill(Vec2::ZERO, Vec2::new(100.0, 100.0));
assert_eq!(mesh.vertex_count(), 4);
assert_eq!(mesh.index_count(), 6);
assert_eq!(mesh.triangle_count(), 2);
}
#[test]
fn test_line_tessellation() {
let tessellator = Tessellator::new();
let mesh = tessellator.tessellate_line(Vec2::ZERO, Vec2::new(100.0, 0.0), 2.0);
assert_eq!(mesh.vertex_count(), 4);
assert_eq!(mesh.triangle_count(), 2);
}
#[test]
fn test_path_fill_tessellation() {
let mut tessellator = Tessellator::new();
let mut builder = PathBuilder::new();
builder.rect(Vec2::ZERO, Vec2::new(100.0, 100.0));
let path = builder.build();
let mesh = tessellator.tessellate_fill(&path, FillRule::NonZero);
assert!(!mesh.is_empty());
}
#[test]
fn test_circle_tessellation() {
let mut tessellator = Tessellator::new();
let shape = Shape::circle(Vec2::new(50.0, 50.0), 25.0);
let mesh = tessellator.tessellate_shape_fill(&shape, FillRule::NonZero);
assert!(!mesh.is_empty());
assert!(mesh.vertex_count() > 4);
}
}