#![allow(clippy::needless_range_loop)]
use crate::geom::arrayvec::ArrayVec;
use crate::geom::utils::tangent;
use crate::geom::{CubicBezierSegment, Line, LineSegment, QuadraticBezierSegment};
use crate::math::*;
use crate::math_utils::compute_normal;
use crate::path::builder::{Build, NoAttributes, PathBuilder};
use crate::path::polygon::Polygon;
use crate::path::private::DebugValidator;
use crate::path::{
AttributeStore, Attributes, EndpointId, IdEvent, PathEvent, PathSlice, PositionStore, Winding,
};
use crate::{
LineCap, LineJoin, Side, SimpleAttributeStore, StrokeGeometryBuilder, StrokeOptions,
TessellationError, TessellationResult, VertexId, VertexSource,
};
use core::f32::consts::PI;
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
const SIDE_POSITIVE: usize = 0;
const SIDE_NEGATIVE: usize = 1;
macro_rules! nan_check {
($($v:expr),+) => { $(debug_assert!(!$v.is_nan());)+ };
}
#[derive(Default)]
pub struct StrokeTessellator {
attrib_buffer: Vec<f32>,
builder_attrib_store: SimpleAttributeStore,
}
impl StrokeTessellator {
pub fn new() -> Self {
StrokeTessellator {
attrib_buffer: Vec::new(),
builder_attrib_store: SimpleAttributeStore::new(0),
}
}
pub fn tessellate(
&mut self,
input: impl IntoIterator<Item = PathEvent>,
options: &StrokeOptions,
builder: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
debug_assert!(
options.variable_line_width.is_none(),
"Variable line width requires custom attributes. Try tessellate_with_ids or tessellate_path",
);
let mut buffer = Vec::new();
let builder = StrokeBuilderImpl::new(options, &mut buffer, builder);
builder.tessellate_fw(input)
}
pub fn tessellate_with_ids(
&mut self,
path: impl IntoIterator<Item = IdEvent>,
positions: &impl PositionStore,
custom_attributes: Option<&dyn AttributeStore>,
options: &StrokeOptions,
output: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
let custom_attributes = custom_attributes.unwrap_or(&());
self.attrib_buffer.clear();
for _ in 0..custom_attributes.num_attributes() {
self.attrib_buffer.push(0.0);
}
let builder = StrokeBuilderImpl::new(options, &mut self.attrib_buffer, output);
builder.tessellate_with_ids(path, positions, custom_attributes)
}
pub fn tessellate_path<'l>(
&'l mut self,
path: impl Into<PathSlice<'l>>,
options: &'l StrokeOptions,
builder: &'l mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
let path = path.into();
if path.num_attributes() > 0 {
self.tessellate_with_ids(path.id_iter(), &path, Some(&path), options, builder)
} else {
self.tessellate(path.iter(), options, builder)
}
}
pub fn builder<'l>(
&'l mut self,
options: &'l StrokeOptions,
output: &'l mut dyn StrokeGeometryBuilder,
) -> NoAttributes<StrokeBuilder<'l>> {
self.builder_attrib_store.reset(0);
self.attrib_buffer.clear();
NoAttributes::wrap(StrokeBuilder::new(
options,
&mut self.attrib_buffer,
&mut self.builder_attrib_store,
output,
))
}
pub fn builder_with_attributes<'l>(
&'l mut self,
num_attributes: usize,
options: &'l StrokeOptions,
output: &'l mut dyn StrokeGeometryBuilder,
) -> StrokeBuilder<'l> {
self.builder_attrib_store.reset(num_attributes);
self.attrib_buffer.clear();
for _ in 0..num_attributes {
self.attrib_buffer.push(0.0);
}
StrokeBuilder::new(
options,
&mut self.attrib_buffer,
&mut self.builder_attrib_store,
output,
)
}
pub fn tessellate_polygon(
&mut self,
polygon: Polygon<Point>,
options: &StrokeOptions,
output: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
self.tessellate(polygon.path_events(), options, output)
}
pub fn tessellate_rectangle(
&mut self,
rect: &Box2D,
options: &StrokeOptions,
output: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
assert!(options.variable_line_width.is_none());
let mut builder = self.builder(options, output);
builder.add_rectangle(rect, Winding::Positive);
builder.build()
}
pub fn tessellate_circle(
&mut self,
center: Point,
radius: f32,
options: &StrokeOptions,
output: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
let mut builder = self.builder(options, output);
builder.add_circle(center, radius, Winding::Positive);
builder.build()
}
pub fn tessellate_ellipse(
&mut self,
center: Point,
radii: Vector,
x_rotation: Angle,
winding: Winding,
options: &StrokeOptions,
output: &mut dyn StrokeGeometryBuilder,
) -> TessellationResult {
let mut builder = self.builder(options, output);
builder.add_ellipse(center, radii, x_rotation, winding);
builder.build()
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct SidePoints {
prev: Point,
next: Point,
single_vertex: Option<Point>,
prev_vertex: VertexId,
next_vertex: VertexId,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct EndpointData {
pub position: Point,
pub half_width: f32,
pub advancement: f32,
pub line_join: LineJoin,
pub src: VertexSource,
pub side_points: [SidePoints; 2],
pub fold: [bool; 2],
pub is_flattening_step: bool,
}
impl Default for EndpointData {
fn default() -> Self {
EndpointData {
position: Point::zero(),
half_width: f32::NAN,
advancement: f32::NAN,
line_join: LineJoin::Miter,
src: VertexSource::Endpoint {
id: EndpointId::INVALID,
},
side_points: [SidePoints {
prev: point(f32::NAN, f32::NAN),
prev_vertex: VertexId(u32::MAX),
next: point(f32::NAN, f32::NAN),
next_vertex: VertexId(u32::MAX),
single_vertex: None,
}; 2],
fold: [false, false],
is_flattening_step: false,
}
}
}
pub struct StrokeBuilder<'l> {
builder: StrokeBuilderImpl<'l>,
attrib_store: &'l mut SimpleAttributeStore,
validator: DebugValidator,
prev: (Point, EndpointId, f32),
}
impl<'l> StrokeBuilder<'l> {
pub(crate) fn new(
options: &StrokeOptions,
attrib_buffer: &'l mut Vec<f32>,
attrib_store: &'l mut SimpleAttributeStore,
output: &'l mut dyn StrokeGeometryBuilder,
) -> Self {
StrokeBuilder {
builder: StrokeBuilderImpl::new(options, attrib_buffer, output),
attrib_store,
validator: DebugValidator::new(),
prev: (Point::zero(), EndpointId::INVALID, 0.0),
}
}
#[inline]
pub fn set_line_join(&mut self, join: LineJoin) {
self.builder.options.line_join = join;
}
#[inline]
pub fn set_start_cap(&mut self, cap: LineCap) {
self.builder.options.start_cap = cap;
}
#[inline]
pub fn set_end_cap(&mut self, cap: LineCap) {
self.builder.options.end_cap = cap;
}
#[inline]
pub fn set_miter_limit(&mut self, limit: f32) {
self.builder.options.miter_limit = limit;
}
fn get_width(&self, attributes: Attributes) -> f32 {
if let Some(idx) = self.builder.options.variable_line_width {
self.builder.options.line_width * attributes[idx]
} else {
self.builder.options.line_width
}
}
}
impl<'l> PathBuilder for StrokeBuilder<'l> {
fn num_attributes(&self) -> usize {
self.attrib_store.num_attributes()
}
fn begin(&mut self, to: Point, attributes: Attributes) -> EndpointId {
self.validator.begin();
let id = self.attrib_store.add(attributes);
if let Some(attrib_index) = self.builder.options.variable_line_width {
let width = self.builder.options.line_width * attributes[attrib_index];
self.builder.begin(to, id, width, self.attrib_store);
self.prev = (to, id, width);
} else {
self.builder.begin_fw(to, id, self.attrib_store);
self.prev = (to, id, self.builder.options.line_width);
}
id
}
fn end(&mut self, close: bool) {
self.validator.end();
self.builder.end(close, self.attrib_store);
}
fn line_to(&mut self, to: Point, attributes: Attributes) -> EndpointId {
let id = self.attrib_store.add(attributes);
self.validator.edge();
if let Some(attrib_index) = self.builder.options.variable_line_width {
let width = self.builder.options.line_width * attributes[attrib_index];
self.builder.line_to(to, id, width, self.attrib_store);
self.prev = (to, id, width);
} else {
self.builder.line_to_fw(to, id, self.attrib_store);
self.prev = (to, id, self.builder.options.line_width);
}
id
}
fn quadratic_bezier_to(
&mut self,
ctrl: Point,
to: Point,
attributes: Attributes,
) -> EndpointId {
self.validator.edge();
let (from, from_id, start_width) = self.prev;
let to_id = self.attrib_store.add(attributes);
let curve = QuadraticBezierSegment { from, ctrl, to };
if let Some(attrib_index) = self.builder.options.variable_line_width {
let end_width = self.builder.options.line_width * attributes[attrib_index];
self.builder.quadratic_bezier_to(
&curve,
from_id,
to_id,
start_width,
end_width,
self.attrib_store,
);
self.prev = (to, to_id, end_width);
} else {
self.builder
.quadratic_bezier_to_fw(&curve, from_id, to_id, self.attrib_store);
self.prev = (to, to_id, self.builder.options.line_width);
}
to_id
}
fn cubic_bezier_to(
&mut self,
ctrl1: Point,
ctrl2: Point,
to: Point,
attributes: Attributes,
) -> EndpointId {
self.validator.edge();
let (from, from_id, start_width) = self.prev;
let to_id = self.attrib_store.add(attributes);
let curve = CubicBezierSegment {
from,
ctrl1,
ctrl2,
to,
};
if let Some(attrib_index) = self.builder.options.variable_line_width {
let end_width = self.builder.options.line_width * attributes[attrib_index];
self.builder.cubic_bezier_to(
&curve,
from_id,
to_id,
start_width,
end_width,
self.attrib_store,
);
self.prev = (to, to_id, end_width);
} else {
self.builder
.cubic_bezier_to_fw(&curve, from_id, to_id, self.attrib_store);
self.prev = (to, to_id, self.builder.options.line_width);
}
to_id
}
fn add_rectangle(&mut self, rect: &Box2D, winding: Winding, attributes: Attributes) {
let threshold = match self.builder.options.line_join {
LineJoin::Miter => 1.0,
_ => 0.05,
} * self.builder.options.line_width;
if self.builder.options.variable_line_width.is_none()
&& (rect.width().abs() < threshold || rect.height().abs() < threshold)
{
approximate_thin_rectangle(self, rect, attributes);
return;
}
match winding {
Winding::Positive => self.add_polygon(
Polygon {
points: &[
rect.min,
point(rect.max.x, rect.min.y),
rect.max,
point(rect.min.x, rect.max.y),
],
closed: true,
},
attributes,
),
Winding::Negative => self.add_polygon(
Polygon {
points: &[
rect.min,
point(rect.min.x, rect.max.y),
rect.max,
point(rect.max.x, rect.min.y),
],
closed: true,
},
attributes,
),
};
}
}
impl<'l> Build for StrokeBuilder<'l> {
type PathType = TessellationResult;
fn build(self) -> TessellationResult {
self.builder.build()
}
}
pub(crate) struct StrokeBuilderImpl<'l> {
options: StrokeOptions,
pub(crate) error: Option<TessellationError>,
pub(crate) output: &'l mut dyn StrokeGeometryBuilder,
vertex: StrokeVertexData<'l>,
point_buffer: PointBuffer,
firsts: ArrayVec<EndpointData, 2>,
previous: Option<EndpointData>,
sub_path_start_advancement: f32,
square_merge_threshold: f32,
may_need_empty_cap: bool,
}
impl<'l> StrokeBuilderImpl<'l> {
pub(crate) fn new(
options: &StrokeOptions,
attrib_buffer: &'l mut Vec<f32>,
output: &'l mut dyn StrokeGeometryBuilder,
) -> Self {
output.begin_geometry();
let square_merge_threshold = (options.tolerance * options.tolerance * 0.5)
.min(options.line_width * options.line_width * 0.05)
.max(1e-8);
let zero = Point::new(0.0, 0.0);
StrokeBuilderImpl {
options: *options,
error: None,
output,
vertex: StrokeVertexData {
position_on_path: zero,
normal: vector(0.0, 0.0),
half_width: options.line_width * 0.5,
advancement: 0.0,
buffer: attrib_buffer,
side: Side::Negative,
src: VertexSource::Endpoint {
id: EndpointId::INVALID,
},
buffer_is_valid: false,
},
point_buffer: PointBuffer::new(),
firsts: ArrayVec::new(),
previous: None,
sub_path_start_advancement: 0.0,
square_merge_threshold,
may_need_empty_cap: false,
}
}
#[cold]
pub(crate) fn error<E: Into<TessellationError>>(&mut self, e: E) {
if self.error.is_none() {
self.error = Some(e.into());
}
}
pub(crate) fn tessellate_with_ids(
self,
path: impl IntoIterator<Item = IdEvent>,
positions: &impl PositionStore,
attributes: &dyn AttributeStore,
) -> TessellationResult {
if self.options.variable_line_width.is_some() {
self.tessellate_with_ids_vw(path, positions, attributes)
} else {
self.tessellate_with_ids_fw(path, positions, attributes)
}
}
fn tessellate_with_ids_vw(
mut self,
path: impl IntoIterator<Item = IdEvent>,
positions: &impl PositionStore,
attributes: &dyn AttributeStore,
) -> TessellationResult {
let base_width = self.options.line_width;
let attrib_index = self.options.variable_line_width.unwrap();
let mut validator = DebugValidator::new();
let mut current_endpoint = EndpointId(u32::MAX);
let mut current_position = point(f32::NAN, f32::NAN);
for evt in path.into_iter() {
match evt {
IdEvent::Begin { at } => {
validator.begin();
let half_width = base_width * attributes.get(at)[attrib_index] * 0.5;
current_endpoint = at;
current_position = positions.get_endpoint(at);
self.may_need_empty_cap = false;
self.step(
EndpointData {
position: current_position,
half_width,
advancement: self.sub_path_start_advancement,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: at },
..Default::default()
},
attributes,
);
}
IdEvent::Line { to, .. } => {
validator.edge();
let half_width = base_width * attributes.get(to)[attrib_index] * 0.5;
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.step(
EndpointData {
position: current_position,
half_width,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: to },
..Default::default()
},
attributes,
);
}
IdEvent::Quadratic { ctrl, to, .. } => {
validator.edge();
let start_width = base_width * attributes.get(current_endpoint)[attrib_index];
let end_width = base_width * attributes.get(to)[attrib_index];
let from = current_endpoint;
let from_pos = current_position;
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.quadratic_bezier_to(
&QuadraticBezierSegment {
from: from_pos,
ctrl: positions.get_control_point(ctrl),
to: current_position,
},
from,
to,
start_width,
end_width,
attributes,
);
}
IdEvent::Cubic {
ctrl1, ctrl2, to, ..
} => {
validator.edge();
let start_width = base_width * attributes.get(current_endpoint)[attrib_index];
let end_width = base_width * attributes.get(to)[attrib_index];
let from = current_endpoint;
let from_pos = current_position;
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.cubic_bezier_to(
&CubicBezierSegment {
from: from_pos,
ctrl1: positions.get_control_point(ctrl1),
ctrl2: positions.get_control_point(ctrl2),
to: current_position,
},
from,
to,
start_width,
end_width,
attributes,
);
}
IdEvent::End { close, .. } => {
validator.end();
self.end(close, attributes);
}
}
if let Some(err) = self.error {
self.output.abort_geometry();
return Err(err);
}
}
validator.build();
self.build()
}
fn tessellate_with_ids_fw(
mut self,
path: impl IntoIterator<Item = IdEvent>,
positions: &impl PositionStore,
attributes: &dyn AttributeStore,
) -> TessellationResult {
let mut validator = DebugValidator::new();
let mut current_endpoint = EndpointId(u32::MAX);
let mut current_position = point(f32::NAN, f32::NAN);
let half_width = self.options.line_width * 0.5;
for evt in path.into_iter() {
match evt {
IdEvent::Begin { at } => {
validator.begin();
current_endpoint = at;
current_position = positions.get_endpoint(at);
self.may_need_empty_cap = false;
self.fixed_width_step(
EndpointData {
position: current_position,
half_width,
advancement: self.sub_path_start_advancement,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: at },
..Default::default()
},
attributes,
);
}
IdEvent::Line { to, .. } => {
validator.edge();
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.fixed_width_step(
EndpointData {
position: current_position,
half_width,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: to },
..Default::default()
},
attributes,
);
}
IdEvent::Quadratic { ctrl, to, .. } => {
validator.edge();
let from = current_endpoint;
let from_pos = current_position;
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.quadratic_bezier_to_fw(
&QuadraticBezierSegment {
from: from_pos,
ctrl: positions.get_control_point(ctrl),
to: current_position,
},
from,
to,
attributes,
);
}
IdEvent::Cubic {
ctrl1, ctrl2, to, ..
} => {
validator.edge();
let from = current_endpoint;
let from_pos = current_position;
current_endpoint = to;
current_position = positions.get_endpoint(to);
self.cubic_bezier_to_fw(
&CubicBezierSegment {
from: from_pos,
ctrl1: positions.get_control_point(ctrl1),
ctrl2: positions.get_control_point(ctrl2),
to: current_position,
},
from,
to,
attributes,
);
}
IdEvent::End { close, .. } => {
validator.end();
self.end(close, attributes);
}
}
if let Some(err) = self.error {
self.output.abort_geometry();
return Err(err);
}
}
validator.build();
self.build()
}
pub(crate) fn tessellate_fw(
mut self,
input: impl IntoIterator<Item = PathEvent>,
) -> TessellationResult {
self.options.variable_line_width = None;
let mut validator = DebugValidator::new();
let mut id = EndpointId(0);
let mut current_position = point(f32::NAN, f32::NAN);
for evt in input {
match evt {
PathEvent::Begin { at } => {
validator.begin();
current_position = at;
self.begin_fw(at, id, &());
id.0 += 1;
}
PathEvent::Line { to, .. } => {
validator.edge();
current_position = to;
self.line_to_fw(to, id, &());
id.0 += 1;
}
PathEvent::Quadratic { ctrl, to, .. } => {
validator.edge();
let from = current_position;
current_position = to;
let prev_id = EndpointId(id.0 - 1);
self.quadratic_bezier_to_fw(
&QuadraticBezierSegment { from, ctrl, to },
prev_id,
id,
&(),
);
id.0 += 1;
}
PathEvent::Cubic {
ctrl1, ctrl2, to, ..
} => {
validator.edge();
let prev_id = EndpointId(id.0 - 1);
let from = current_position;
current_position = to;
self.cubic_bezier_to_fw(
&CubicBezierSegment {
from,
ctrl1,
ctrl2,
to,
},
prev_id,
id,
&(),
);
id.0 += 1;
}
PathEvent::End { close, .. } => {
validator.end();
self.end(close, &());
}
}
if let Some(err) = self.error {
self.output.abort_geometry();
return Err(err);
}
}
validator.build();
self.build()
}
pub(crate) fn begin(
&mut self,
position: Point,
endpoint: EndpointId,
width: f32,
attributes: &dyn AttributeStore,
) {
self.may_need_empty_cap = false;
let half_width = width * 0.5;
self.step(
EndpointData {
position,
half_width,
advancement: self.sub_path_start_advancement,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: endpoint },
..Default::default()
},
attributes,
);
}
pub(crate) fn line_to(
&mut self,
to: Point,
endpoint: EndpointId,
width: f32,
attributes: &dyn AttributeStore,
) {
let half_width = width * 0.5;
self.step(
EndpointData {
position: to,
half_width,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: endpoint },
..Default::default()
},
attributes,
);
}
pub(crate) fn quadratic_bezier_to(
&mut self,
curve: &QuadraticBezierSegment<f32>,
from_id: EndpointId,
to_id: EndpointId,
start_width: f32,
end_width: f32,
attributes: &dyn AttributeStore,
) {
flatten_quad(
curve,
self.options.tolerance,
&mut |position, t, is_flattening_step| {
let src = if t == 1.0 {
VertexSource::Endpoint { id: to_id }
} else {
VertexSource::Edge {
from: from_id,
to: to_id,
t,
}
};
self.step(
EndpointData {
position,
half_width: (start_width * (1.0 - t) + end_width * t) * 0.5,
line_join: self.options.line_join,
src,
is_flattening_step,
..Default::default()
},
attributes,
);
},
);
}
pub(crate) fn cubic_bezier_to(
&mut self,
curve: &CubicBezierSegment<f32>,
from_id: EndpointId,
to_id: EndpointId,
start_width: f32,
end_width: f32,
attributes: &dyn AttributeStore,
) {
curve.for_each_flattened_with_t(self.options.tolerance, &mut |line, t| {
let is_flattening_step = t.end != 1.0;
let src = if is_flattening_step {
VertexSource::Edge {
from: from_id,
to: to_id,
t: t.end,
}
} else {
VertexSource::Endpoint { id: to_id }
};
self.step(
EndpointData {
position: line.to,
half_width: (start_width * (1.0 - t.end) + end_width * t.end) * 0.5,
line_join: self.options.line_join,
src,
is_flattening_step,
..Default::default()
},
attributes,
);
});
}
pub(crate) fn begin_fw(
&mut self,
position: Point,
endpoint: EndpointId,
attributes: &dyn AttributeStore,
) {
self.may_need_empty_cap = false;
self.fixed_width_step(
EndpointData {
position,
half_width: self.options.line_width * 0.5,
advancement: self.sub_path_start_advancement,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: endpoint },
..Default::default()
},
attributes,
);
}
pub(crate) fn line_to_fw(
&mut self,
to: Point,
endpoint: EndpointId,
attributes: &dyn AttributeStore,
) {
let half_width = self.options.line_width * 0.5;
self.fixed_width_step(
EndpointData {
position: to,
half_width,
line_join: self.options.line_join,
src: VertexSource::Endpoint { id: endpoint },
..Default::default()
},
attributes,
);
}
pub(crate) fn quadratic_bezier_to_fw(
&mut self,
curve: &QuadraticBezierSegment<f32>,
from_id: EndpointId,
to_id: EndpointId,
attributes: &dyn AttributeStore,
) {
let half_width = self.options.line_width * 0.5;
flatten_quad(
curve,
self.options.tolerance,
&mut |position, t, is_flattening_step| {
let src = if t == 1.0 {
VertexSource::Endpoint { id: to_id }
} else {
VertexSource::Edge {
from: from_id,
to: to_id,
t,
}
};
self.fixed_width_step(
EndpointData {
position,
half_width,
line_join: self.options.line_join,
src,
is_flattening_step,
..Default::default()
},
attributes,
);
},
);
}
pub(crate) fn cubic_bezier_to_fw(
&mut self,
curve: &CubicBezierSegment<f32>,
from_id: EndpointId,
to_id: EndpointId,
attributes: &dyn AttributeStore,
) {
let half_width = self.options.line_width * 0.5;
curve.for_each_flattened_with_t(self.options.tolerance, &mut |line, t| {
let is_flattening_step = t.end != 1.0;
let src = if is_flattening_step {
VertexSource::Edge {
from: from_id,
to: to_id,
t: t.end,
}
} else {
VertexSource::Endpoint { id: to_id }
};
self.fixed_width_step(
EndpointData {
position: line.to,
half_width,
line_join: self.options.line_join,
src,
is_flattening_step,
..Default::default()
},
attributes,
);
});
}
pub(crate) fn end(&mut self, close: bool, attributes: &dyn AttributeStore) {
self.may_need_empty_cap |= close && self.point_buffer.count() == 1;
let e = if close && self.point_buffer.count() > 2 {
self.close(attributes)
} else {
self.end_with_caps(attributes)
};
if let Err(e) = e {
self.error(e);
}
self.point_buffer.clear();
self.firsts.clear();
}
pub(crate) fn build(self) -> TessellationResult {
if let Some(err) = self.error {
self.output.abort_geometry();
return Err(err);
}
self.output.end_geometry();
Ok(())
}
fn close(&mut self, attributes: &dyn AttributeStore) -> Result<(), TessellationError> {
if self.point_buffer.count() == 1 {
self.tessellate_empty_cap(attributes)?;
}
if self.point_buffer.count() <= 2 {
return Ok(());
}
assert!(!self.firsts.is_empty());
let mut p = self.firsts[0];
let advancement = p.advancement;
p.advancement = f32::NAN;
let segment_added = if self.options.variable_line_width.is_some() {
self.step_impl(p, attributes)?
} else {
self.fixed_width_step_impl(p, attributes)?
};
if !segment_added {
self.point_buffer.last_mut().position = p.position;
}
if self.firsts.len() >= 2 {
let p2 = self.firsts[1];
if self.options.variable_line_width.is_some() {
self.step_impl(p2, attributes)?;
} else {
self.fixed_width_step_impl(p2, attributes)?;
}
let (p0, p1) = self.point_buffer.last_two_mut();
self.vertex.src = p0.src;
self.vertex.position_on_path = p0.position;
self.vertex.half_width = p0.half_width;
self.vertex.advancement = advancement;
self.vertex.buffer_is_valid = false;
for side in 0..2 {
self.vertex.side = if side == SIDE_POSITIVE {
Side::Positive
} else {
Side::Negative
};
self.vertex.normal = if let Some(pos) = p0.side_points[side].single_vertex {
(pos - p0.position) / p0.half_width
} else {
(p0.side_points[side].next - p0.position) / p0.half_width
};
let vertex = self
.output
.add_stroke_vertex(StrokeVertex(&mut self.vertex, attributes))?;
p0.side_points[side].next_vertex = vertex;
}
add_edge_triangles(p0, p1, self.output);
}
Ok(())
}
#[cold]
fn tessellate_empty_cap(
&mut self,
attributes: &dyn AttributeStore,
) -> Result<(), TessellationError> {
let point = self.point_buffer.get(0);
self.vertex.advancement = point.advancement;
self.vertex.src = point.src;
self.vertex.half_width = point.half_width;
match self.options.start_cap {
LineCap::Square => {
crate::stroke::tessellate_empty_square_cap(
point.position,
&mut self.vertex,
attributes,
self.output,
)?;
}
LineCap::Round => {
crate::stroke::tessellate_empty_round_cap(
point.position,
&self.options,
&mut self.vertex,
attributes,
self.output,
)?;
}
_ => {}
}
Ok(())
}
fn points_are_too_close(&self, p0: Point, p1: Point) -> bool {
(p0 - p1).square_length() < self.square_merge_threshold
}
fn end_with_caps(&mut self, attributes: &dyn AttributeStore) -> Result<(), TessellationError> {
let count = self.point_buffer.count();
if self.may_need_empty_cap && count == 1 {
return self.tessellate_empty_cap(attributes);
}
if count >= 2 {
let (p0, p1) = self.point_buffer.last_two_mut();
let mut p0 = *p0;
let mut p1 = *p1;
if self.options.variable_line_width.is_none() {
let tangent = (p1.position - p0.position).normalize();
let n = vector(-tangent.y, tangent.x) * p1.half_width;
p1.side_points[SIDE_POSITIVE].prev = p1.position + n;
p1.side_points[SIDE_NEGATIVE].prev = p1.position - n;
}
let is_first = count == 2;
tessellate_last_edge(
&p0,
&mut p1,
is_first,
&self.options,
&mut self.vertex,
attributes,
self.output,
)?;
self.sub_path_start_advancement = p1.advancement;
if count > 2 {
p0 = self.firsts[0];
p1 = self.firsts[1];
}
tessellate_first_edge(
&mut p0,
&p1,
&self.options,
&mut self.vertex,
attributes,
self.output,
)?;
}
Ok(())
}
pub(crate) fn step_impl(
&mut self,
mut next: EndpointData,
attributes: &dyn AttributeStore,
) -> Result<bool, TessellationError> {
let count = self.point_buffer.count();
debug_assert!(self.options.variable_line_width.is_some());
if count > 0 && self.points_are_too_close(self.point_buffer.last().position, next.position)
{
if count == 1 {
self.may_need_empty_cap = true;
}
return Ok(false);
}
if count > 0 {
let join = self.point_buffer.last_mut();
if !join.is_flattening_step || !next.is_flattening_step {
compute_edge_attachment_positions(join, &mut next);
}
}
let mut skip = false;
if count > 1 {
let (prev, join) = self.point_buffer.last_two_mut();
nan_check!(join.advancement);
nan_check!(prev.advancement);
self.vertex.src = join.src;
self.vertex.position_on_path = join.position;
self.vertex.half_width = join.half_width;
self.vertex.advancement = join.advancement;
self.vertex.buffer_is_valid = false;
let fast_path = if join.is_flattening_step {
let v0 = join.position - prev.position;
let v1 = next.position - join.position;
v0.dot(v1) > 0.0
} else {
false
};
if fast_path {
join.line_join = LineJoin::Miter;
skip = flattened_step(
prev,
join,
&mut next,
&mut self.vertex,
attributes,
self.output,
)?;
} else {
compute_join_side_positions(
prev,
join,
&next,
self.options.miter_limit,
SIDE_POSITIVE,
);
compute_join_side_positions(
prev,
join,
&next,
self.options.miter_limit,
SIDE_NEGATIVE,
);
if join.side_points[SIDE_POSITIVE].single_vertex.is_some() {
join.fold[SIDE_NEGATIVE] = false;
}
if join.side_points[SIDE_NEGATIVE].single_vertex.is_some() {
join.fold[SIDE_POSITIVE] = false;
}
add_join_base_vertices(
join,
&mut self.vertex,
attributes,
self.output,
Side::Negative,
)?;
add_join_base_vertices(
join,
&mut self.vertex,
attributes,
self.output,
Side::Positive,
)?;
}
if !skip {
if count > 2 {
add_edge_triangles(prev, join, self.output);
}
tessellate_join(
join,
&self.options,
&mut self.vertex,
attributes,
self.output,
)?;
if count == 2 {
self.firsts.push(*prev);
self.firsts.push(*join);
}
}
}
if skip {
self.point_buffer.replace_last(next);
} else {
self.point_buffer.push(next);
}
Ok(true)
}
#[cfg_attr(feature = "profiling", inline(never))]
pub(crate) fn step(&mut self, next: EndpointData, attributes: &dyn AttributeStore) {
if let Err(e) = self.step_impl(next, attributes) {
self.error(e);
}
}
pub(crate) fn fixed_width_step_impl(
&mut self,
mut next: EndpointData,
attributes: &dyn AttributeStore,
) -> Result<bool, TessellationError> {
let count = self.point_buffer.count();
debug_assert!(self.options.variable_line_width.is_none());
if count > 0 {
if self.points_are_too_close(self.point_buffer.last().position, next.position) {
if count == 1 {
self.may_need_empty_cap = true;
}
return Ok(false);
}
if count == 1 {
let first = self.point_buffer.last_mut();
let edge = next.position - first.position;
let length = edge.length();
if next.advancement.is_nan() {
nan_check!(first.advancement);
nan_check!(length);
next.advancement = first.advancement + length;
}
let tangent = edge / length;
let n = vector(-tangent.y, tangent.x) * next.half_width;
first.side_points[SIDE_POSITIVE].next = first.position + n;
first.side_points[SIDE_NEGATIVE].next = first.position - n;
}
}
if count > 1 {
let (prev, join) = self.point_buffer.last_two_mut();
self.vertex.src = join.src;
self.vertex.position_on_path = join.position;
self.vertex.half_width = join.half_width;
self.vertex.buffer_is_valid = false;
let fast_path = if join.is_flattening_step {
let v0 = join.position - prev.position;
let v1 = next.position - join.position;
v0.dot(v1) > 0.0
} else {
false
};
if fast_path {
join.line_join = LineJoin::Miter;
flattened_step(
prev,
join,
&mut next,
&mut self.vertex,
attributes,
self.output,
)?;
} else {
compute_join_side_positions_fixed_width(
prev,
join,
&next,
self.options.miter_limit,
&mut self.vertex,
)?;
add_join_base_vertices(
join,
&mut self.vertex,
attributes,
self.output,
Side::Negative,
)?;
add_join_base_vertices(
join,
&mut self.vertex,
attributes,
self.output,
Side::Positive,
)?;
}
if count > 2 {
add_edge_triangles(prev, join, self.output);
}
tessellate_join(
join,
&self.options,
&mut self.vertex,
attributes,
self.output,
)?;
if count == 2 {
self.firsts.push(*prev);
self.firsts.push(*join);
}
}
self.point_buffer.push(next);
Ok(true)
}
#[cfg_attr(feature = "profiling", inline(never))]
pub(crate) fn fixed_width_step(&mut self, next: EndpointData, attributes: &dyn AttributeStore) {
if let Err(e) = self.fixed_width_step_impl(next, attributes) {
self.error(e);
}
}
}
#[cfg_attr(feature = "profiling", inline(never))]
fn compute_join_side_positions_fixed_width(
prev: &EndpointData,
join: &mut EndpointData,
next: &EndpointData,
miter_limit: f32,
vertex: &mut StrokeVertexData,
) -> Result<(), TessellationError> {
let prev_tangent = join.position - prev.position;
let next_tangent = next.position - join.position;
let prev_length = prev_tangent.length();
let next_length = next_tangent.length();
let prev_tangent = prev_tangent / prev_length;
let next_tangent = next_tangent / next_length;
if join.advancement.is_nan() {
nan_check!(prev.advancement);
nan_check!(prev_length);
join.advancement = prev.advancement + prev_length;
}
vertex.advancement = join.advancement;
let normal = compute_normal(prev_tangent, next_tangent);
let (front_side, front_normal) = if prev_tangent.cross(next_tangent) >= 0.0 {
(SIDE_NEGATIVE, -normal)
} else {
(SIDE_POSITIVE, normal)
};
let back_side = 1 - front_side;
let extruded_normal = front_normal * vertex.half_width;
let unclipped_miter = (join.line_join == LineJoin::Miter
|| join.line_join == LineJoin::MiterClip)
&& !miter_limit_is_exceeded(front_normal, miter_limit);
let mut fold = false;
let angle_is_sharp = next_tangent.dot(prev_tangent) < 0.0;
if !unclipped_miter && angle_is_sharp {
let d_next = extruded_normal.dot(-next_tangent) - next_length;
let d_prev = extruded_normal.dot(prev_tangent) - prev_length;
if d_next.min(d_prev) > 0.0 || normal.square_length() < 1e-5 {
join.fold[front_side] = true;
fold = true;
}
}
let n0 = vector(-prev_tangent.y, prev_tangent.x) * vertex.half_width;
let n1 = vector(-next_tangent.y, next_tangent.x) * vertex.half_width;
join.side_points[SIDE_POSITIVE].prev = join.position + n0;
join.side_points[SIDE_POSITIVE].next = join.position + n1;
join.side_points[SIDE_NEGATIVE].prev = join.position - n0;
join.side_points[SIDE_NEGATIVE].next = join.position - n1;
if !fold {
let miter_pos = [
join.position + normal * vertex.half_width,
join.position - normal * vertex.half_width,
];
join.side_points[back_side].single_vertex = Some(miter_pos[back_side]);
if unclipped_miter {
join.side_points[front_side].single_vertex = Some(miter_pos[front_side]);
} else if join.line_join == LineJoin::MiterClip {
let n0 = join.side_points[front_side].prev - join.position;
let n1 = join.side_points[front_side].next - join.position;
let (prev_normal, next_normal) =
get_clip_intersections(n0, n1, front_normal, miter_limit * 0.5 * vertex.half_width);
join.side_points[front_side].prev = join.position + prev_normal;
join.side_points[front_side].next = join.position + next_normal;
}
}
Ok(())
}
#[cfg_attr(feature = "profiling", inline(never))]
fn flattened_step(
prev: &mut EndpointData,
join: &mut EndpointData,
next: &mut EndpointData,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<bool, TessellationError> {
let prev_edge = join.position - prev.position;
let prev_length = prev_edge.length();
let prev_tangent = prev_edge / prev_length;
let next_edge = next.position - join.position;
let next_length = next_edge.length();
let next_tangent = next_edge / next_length;
let normal = compute_normal(prev_tangent, next_tangent);
if join.advancement.is_nan() {
nan_check!(prev.advancement);
nan_check!(prev_length);
join.advancement = prev.advancement + prev_length;
}
if next.advancement.is_nan() {
nan_check!(join.advancement);
nan_check!(next_length);
next.advancement = join.advancement + next_length;
}
vertex.advancement = join.advancement;
let p0 = join.position + normal * vertex.half_width;
nan_check!(p0);
join.side_points[SIDE_POSITIVE].prev = p0;
join.side_points[SIDE_POSITIVE].next = p0;
join.side_points[SIDE_POSITIVE].single_vertex = Some(p0);
let p1 = join.position - normal * vertex.half_width;
nan_check!(p1);
join.side_points[SIDE_NEGATIVE].prev = p1;
join.side_points[SIDE_NEGATIVE].next = p1;
join.side_points[SIDE_NEGATIVE].single_vertex = Some(p1);
let v0 = p0 - prev.side_points[SIDE_POSITIVE].next;
let v1 = p1 - prev.side_points[SIDE_NEGATIVE].next;
if prev_edge.dot(v0) < 0.0 && prev_edge.dot(v1) < 0.0 {
return Ok(true);
}
vertex.normal = normal;
vertex.side = Side::Positive;
let pos_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
vertex.normal = -normal;
vertex.side = Side::Negative;
let neg_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
join.side_points[SIDE_POSITIVE].prev_vertex = pos_vertex;
join.side_points[SIDE_POSITIVE].next_vertex = pos_vertex;
join.side_points[SIDE_NEGATIVE].prev_vertex = neg_vertex;
join.side_points[SIDE_NEGATIVE].next_vertex = neg_vertex;
Ok(false)
}
#[cfg_attr(feature = "profiling", inline(never))]
fn compute_edge_attachment_positions(p0: &mut EndpointData, p1: &mut EndpointData) {
let edge = p1.position - p0.position;
let d = edge.length();
let edge_angle = edge.angle_from_x_axis().radians;
let sin_vwidth_angle = (p1.half_width - p0.half_width) / d;
let mut vwidth_angle = sin_vwidth_angle.asin();
if vwidth_angle.is_nan() {
vwidth_angle = 0.0;
}
nan_check!(d, p0.half_width, p1.half_width, vwidth_angle);
compute_side_attachment_positions(p0, p1, edge_angle, vwidth_angle, SIDE_POSITIVE);
compute_side_attachment_positions(p0, p1, edge_angle, vwidth_angle, SIDE_NEGATIVE);
if p1.advancement.is_nan() {
nan_check!(p0.advancement, d);
p1.advancement = p0.advancement + d;
}
}
fn compute_side_attachment_positions(
p0: &mut EndpointData,
p1: &mut EndpointData,
edge_angle: f32,
vwidth_angle: f32,
side: usize,
) {
nan_check!(
edge_angle,
vwidth_angle,
p0.position,
p1.position,
p0.half_width,
p1.half_width
);
let nl = side_sign(side);
let normal_angle = edge_angle + nl * (PI * 0.5 + vwidth_angle);
let normal = vector(normal_angle.cos(), normal_angle.sin());
nan_check!(normal);
p0.side_points[side].next = p0.position + normal * p0.half_width;
p1.side_points[side].prev = p1.position + normal * p1.half_width;
nan_check!(p0.side_points[side].next);
nan_check!(p1.side_points[side].prev);
}
#[cfg_attr(feature = "profiling", inline(never))]
fn add_edge_triangles(
p0: &EndpointData,
p1: &EndpointData,
output: &mut dyn StrokeGeometryBuilder,
) {
let mut p0_neg = p0.side_points[SIDE_NEGATIVE].next_vertex;
let mut p0_pos = p0.side_points[SIDE_POSITIVE].next_vertex;
let mut p1_neg = p1.side_points[SIDE_NEGATIVE].prev_vertex;
let mut p1_pos = p1.side_points[SIDE_POSITIVE].prev_vertex;
if p0.fold[SIDE_POSITIVE] {
p0_neg = p0.side_points[SIDE_POSITIVE].prev_vertex;
}
if p0.fold[SIDE_NEGATIVE] {
p0_pos = p0.side_points[SIDE_NEGATIVE].prev_vertex;
}
if p1.fold[SIDE_POSITIVE] {
p1_neg = p1.side_points[SIDE_POSITIVE].next_vertex;
}
if p1.fold[SIDE_NEGATIVE] {
p1_pos = p1.side_points[SIDE_NEGATIVE].next_vertex;
}
if p0_neg == p1_pos {
return;
}
if p0_neg != p0_pos && p0_pos != p1_pos {
output.add_triangle(p0_neg, p0_pos, p1_pos);
}
if p0_neg != p1_neg && p1_pos != p1_neg {
output.add_triangle(p0_neg, p1_pos, p1_neg);
}
}
#[cfg_attr(feature = "profiling", inline(never))]
fn tessellate_join(
join: &mut EndpointData,
options: &StrokeOptions,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
let side_needs_join = [
join.side_points[SIDE_POSITIVE].single_vertex.is_none() && !join.fold[SIDE_NEGATIVE],
join.side_points[SIDE_NEGATIVE].single_vertex.is_none() && !join.fold[SIDE_POSITIVE],
];
if !join.fold[SIDE_POSITIVE] && !join.fold[SIDE_NEGATIVE] {
match side_needs_join {
[true, true] => {
output.add_triangle(
join.side_points[SIDE_POSITIVE].prev_vertex,
join.side_points[SIDE_POSITIVE].next_vertex,
join.side_points[SIDE_NEGATIVE].next_vertex,
);
output.add_triangle(
join.side_points[SIDE_POSITIVE].prev_vertex,
join.side_points[SIDE_NEGATIVE].next_vertex,
join.side_points[SIDE_NEGATIVE].prev_vertex,
);
}
[false, true] => {
output.add_triangle(
join.side_points[SIDE_NEGATIVE].prev_vertex,
join.side_points[SIDE_POSITIVE].prev_vertex,
join.side_points[SIDE_NEGATIVE].next_vertex,
);
}
[true, false] => {
output.add_triangle(
join.side_points[SIDE_NEGATIVE].prev_vertex,
join.side_points[SIDE_POSITIVE].prev_vertex,
join.side_points[SIDE_POSITIVE].next_vertex,
);
}
[false, false] => {}
}
}
for side in 0..2 {
if !side_needs_join[side] {
continue;
}
if join.line_join == LineJoin::Round {
tessellate_round_join(join, side, options, vertex, attributes, output)?;
}
}
Ok(())
}
#[cfg_attr(feature = "profiling", inline(never))]
fn tessellate_round_join(
join: &mut EndpointData,
side: usize,
options: &StrokeOptions,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
let center = join.position;
let radius = join.half_width;
let start_normal = join.side_points[side].prev - center;
let end_normal = join.side_points[side].next - center;
let mut start_vertex = join.side_points[side].prev_vertex;
let mut end_vertex = join.side_points[side].next_vertex;
let angle_sign = if side == SIDE_NEGATIVE { 1.0 } else { -1.0 };
let mut start_angle = start_normal.angle_from_x_axis();
let mut diff = start_angle.angle_to(end_normal.angle_from_x_axis());
if diff.radians * angle_sign < 0.0 {
diff.radians = angle_sign * (2.0 * PI - diff.radians.abs());
}
let mut end_angle = start_angle + diff;
if side == SIDE_NEGATIVE {
core::mem::swap(&mut start_angle, &mut end_angle);
core::mem::swap(&mut start_vertex, &mut end_vertex);
}
let step = circle_flattening_step(radius, options.tolerance);
let num_segments = (diff.radians.abs() / step).ceil();
let num_subdivisions = num_segments.log2().round() as u32;
vertex.side = if side == SIDE_POSITIVE {
Side::Positive
} else {
Side::Negative
};
crate::stroke::tessellate_arc(
(start_angle.radians, end_angle.radians),
start_vertex,
end_vertex,
num_subdivisions,
vertex,
attributes,
output,
)
}
#[cfg_attr(feature = "profiling", inline(never))]
fn add_join_base_vertices(
join: &mut EndpointData,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
side: Side,
) -> Result<(), TessellationError> {
vertex.side = side;
let side = match side {
Side::Positive => SIDE_POSITIVE,
Side::Negative => SIDE_NEGATIVE,
};
if let Some(pos) = join.side_points[side].single_vertex {
vertex.normal = (pos - join.position) / join.half_width;
let vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
join.side_points[side].prev_vertex = vertex;
join.side_points[side].next_vertex = vertex;
} else {
vertex.normal = (join.side_points[side].prev - join.position) / join.half_width;
let prev_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
vertex.normal = (join.side_points[side].next - join.position) / join.half_width;
let next_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
join.side_points[side].prev_vertex = prev_vertex;
join.side_points[side].next_vertex = next_vertex;
}
Ok(())
}
#[cfg_attr(feature = "profiling", inline(never))]
fn compute_join_side_positions(
prev: &EndpointData,
join: &mut EndpointData,
next: &EndpointData,
miter_limit: f32,
side: usize,
) {
nan_check!(join.position);
nan_check!(prev.side_points[side].next);
nan_check!(join.side_points[side].next);
let sign = side_sign(side);
let v0 = (join.side_points[side].prev - prev.side_points[side].next).normalize();
let v1 = (next.side_points[side].prev - join.side_points[side].next).normalize();
let inward = v0.cross(v1) * sign > 0.0;
let forward = v0.dot(v1) > 0.0;
let normal = compute_normal(v0, v1) * sign;
let path_v0 = (join.position - prev.position).normalize();
let path_v1 = (next.position - join.position).normalize();
nan_check!(v0, v1);
let normal_same_side = (v0 + v1).dot(path_v0 + path_v1) >= 0.0;
let angle_is_sharp = inward && !forward && normal_same_side;
if angle_is_sharp {
let extruded_normal = normal * join.half_width;
let prev_length = join.advancement - prev.advancement;
let next_length = next.advancement - join.advancement;
let d_next = extruded_normal.dot(v1) - next_length;
let d_prev = extruded_normal.dot(-v0) - prev_length;
if d_next.min(d_prev) >= 0.0 || normal.square_length() < 1e-5 {
join.fold[side] = true;
}
}
let concave = inward && normal_same_side && !join.fold[side];
if concave
|| ((join.line_join == LineJoin::Miter || join.line_join == LineJoin::MiterClip)
&& !miter_limit_is_exceeded(normal, miter_limit))
{
let p = join.position + normal * join.half_width;
join.side_points[side].single_vertex = Some(p);
} else if join.line_join == LineJoin::MiterClip {
let n0 = join.side_points[side].prev - join.position;
let n1 = join.side_points[side].next - join.position;
let (prev_normal, next_normal) =
get_clip_intersections(n0, n1, normal, miter_limit * 0.5 * join.half_width);
join.side_points[side].prev = join.position + prev_normal;
join.side_points[side].next = join.position + next_normal;
nan_check!(n0, n1, prev_normal, next_normal);
nan_check!(join.side_points[side].prev);
nan_check!(join.side_points[side].next);
}
}
fn tessellate_last_edge(
p0: &EndpointData,
p1: &mut EndpointData,
is_first_edge: bool,
options: &StrokeOptions,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
let v = p1.position - p0.position;
p1.advancement = p0.advancement + v.length();
vertex.src = p1.src;
vertex.position_on_path = p1.position;
vertex.advancement = p1.advancement;
vertex.half_width = p1.half_width;
vertex.buffer_is_valid = false;
let sides = [Side::Positive, Side::Negative];
for side in 0..2 {
vertex.side = sides[side];
vertex.normal = (p1.side_points[side].prev - p1.position) / p1.half_width;
let prev_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
p1.side_points[side].prev_vertex = prev_vertex;
}
if !is_first_edge {
add_edge_triangles(p0, p1, output);
}
if options.end_cap == LineCap::Round {
crate::stroke::tessellate_round_cap(
p1.position,
p1.half_width,
p1.side_points[SIDE_POSITIVE].prev - p1.position,
p1.side_points[SIDE_POSITIVE].prev_vertex,
p1.side_points[SIDE_NEGATIVE].prev_vertex,
v,
options,
false,
vertex,
attributes,
output,
)?;
}
Ok(())
}
fn tessellate_first_edge(
first: &mut EndpointData,
second: &EndpointData,
options: &StrokeOptions,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
vertex.src = first.src;
vertex.position_on_path = first.position;
vertex.advancement = first.advancement;
vertex.half_width = first.half_width;
vertex.buffer_is_valid = false;
let sides = [Side::Positive, Side::Negative];
for side in 0..2 {
let mut side_position = first.side_points[side].next;
let clip = match options.start_cap {
LineCap::Square => Some(first.half_width),
LineCap::Butt => Some(0.0),
_ => None,
};
if let Some(clip) = clip {
let normal = (first.position - second.position).normalize();
let clip_line = Line {
point: first.position + normal * clip,
vector: tangent(normal),
};
let side_line = Line {
point: side_position,
vector: side_position - second.side_points[side].prev,
};
let intersection = clip_line
.to_f64()
.intersection(&side_line.to_f64())
.map(|p| p.to_f32())
.unwrap_or(first.side_points[side].next);
side_position = intersection;
}
vertex.side = sides[side];
vertex.normal = (side_position - first.position) / first.half_width;
first.side_points[side].next_vertex =
output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
}
add_edge_triangles(first, second, output);
match options.start_cap {
LineCap::Round => crate::stroke::tessellate_round_cap(
first.position,
first.half_width,
first.side_points[SIDE_NEGATIVE].next - first.position,
first.side_points[SIDE_NEGATIVE].next_vertex,
first.side_points[SIDE_POSITIVE].next_vertex,
first.position - second.position,
options,
true,
vertex,
attributes,
output,
),
_ => Ok(()),
}
}
#[cfg_attr(feature = "profiling", inline(never))]
fn get_clip_intersections(
previous_normal: Vector,
next_normal: Vector,
normal: Vector,
clip_distance: f32,
) -> (Vector, Vector) {
let clip_line = Line {
point: normal.normalize().to_point() * clip_distance,
vector: tangent(normal),
}
.to_f64();
let prev_line = Line {
point: previous_normal.to_point(),
vector: tangent(previous_normal),
}
.to_f64();
let next_line = Line {
point: next_normal.to_point(),
vector: tangent(next_normal),
}
.to_f64();
let i1 = clip_line
.intersection(&prev_line)
.map(|p| p.to_f32())
.unwrap_or_else(|| normal.to_point())
.to_vector();
let i2 = clip_line
.intersection(&next_line)
.map(|p| p.to_f32())
.unwrap_or_else(|| normal.to_point())
.to_vector();
(i1, i2)
}
fn miter_limit_is_exceeded(normal: Vector, miter_limit: f32) -> bool {
normal.square_length() > miter_limit * miter_limit * 4.0
}
fn side_sign(side: usize) -> f32 {
if side == SIDE_NEGATIVE {
-1.0
} else {
1.0
}
}
#[inline(never)]
fn approximate_thin_rectangle(builder: &mut StrokeBuilder, rect: &Box2D, attributes: Attributes) {
let (from, to, d) = if rect.width() > rect.height() {
let d = rect.height() * 0.5;
let min_x = rect.min.x + d;
let max_x = rect.max.x - d;
let y = (rect.min.y + rect.max.y) * 0.5;
(point(min_x, y), point(max_x, y), d)
} else {
let d = rect.width() * 0.5;
let min_y = rect.min.y + d;
let max_y = rect.max.y - d;
let x = (rect.min.x + rect.max.x) * 0.5;
(point(x, min_y), point(x, max_y), d)
};
let options = builder.builder.options;
let cap = match options.line_join {
LineJoin::Round => LineCap::Round,
_ => LineCap::Square,
};
builder.builder.options.line_width += d;
builder.builder.options.start_cap = cap;
builder.builder.options.end_cap = cap;
builder.add_line_segment(&LineSegment { from, to }, attributes);
builder.builder.options = options;
}
struct PointBuffer {
points: [EndpointData; 3],
start: usize,
count: usize,
}
impl PointBuffer {
fn new() -> Self {
PointBuffer {
points: [EndpointData::default(); 3],
start: 0,
count: 0,
}
}
fn push(&mut self, point: EndpointData) {
if self.count < 3 {
self.points[self.count] = point;
self.count += 1;
return;
}
self.points[self.start] = point;
self.start += 1;
if self.start == 3 {
self.start = 0;
}
}
fn replace_last(&mut self, point: EndpointData) {
let mut idx = self.start;
if idx == 0 {
idx = self.count;
}
self.points[idx - 1] = point;
}
fn clear(&mut self) {
self.count = 0;
self.start = 0;
}
fn count(&self) -> usize {
self.count
}
fn get(&self, idx: usize) -> &EndpointData {
assert!(idx < self.count);
let idx = (idx + self.start) % 3;
&self.points[idx]
}
fn get_reverse(&self, idx: usize) -> &EndpointData {
assert!(idx < self.count);
self.get(self.count - 1 - idx)
}
fn get_mut(&mut self, idx: usize) -> &mut EndpointData {
assert!(idx < self.count);
let idx = (idx + self.start) % 3;
&mut self.points[idx]
}
fn last(&self) -> &EndpointData {
assert!(self.count > 0);
self.get(self.count - 1)
}
fn last_mut(&mut self) -> &mut EndpointData {
assert!(self.count > 0);
self.get_mut(self.count - 1)
}
fn last_two_mut(&mut self) -> (&mut EndpointData, &mut EndpointData) {
assert!(self.count >= 2);
let i0 = (self.start + self.count - 1) % 3;
let i1 = (self.start + self.count - 2) % 3;
unsafe {
(
&mut *(self.points.get_unchecked_mut(i1) as *mut _),
&mut *(self.points.get_unchecked_mut(i0) as *mut _),
)
}
}
}
pub(crate) fn tessellate_round_cap(
center: Point,
radius: f32,
start_normal: Vector,
start_vertex: VertexId,
end_vertex: VertexId,
edge_normal: Vector,
options: &StrokeOptions,
is_start: bool,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
if radius < options.tolerance {
return Ok(());
}
let first_side = if is_start ^ (edge_normal.cross(start_normal) >= 0.0) {
Side::Positive
} else {
Side::Negative
};
let start_angle = start_normal.angle_from_x_axis();
let diff = start_angle.angle_to(edge_normal.angle_from_x_axis());
let mid_angle = start_angle + diff;
let end_angle = mid_angle + diff;
let step = circle_flattening_step(radius, options.tolerance);
let num_segments = (diff.radians.abs() / step).ceil();
let num_subdivisions = num_segments.log2().round() as u32;
vertex.position_on_path = center;
vertex.half_width = radius;
vertex.side = first_side;
vertex.normal = edge_normal.normalize();
let mid_vertex = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
output.add_triangle(start_vertex, mid_vertex, end_vertex);
tessellate_arc(
(start_angle.radians, mid_angle.radians),
start_vertex,
mid_vertex,
num_subdivisions,
vertex,
attributes,
output,
)?;
vertex.side = first_side.opposite();
tessellate_arc(
(mid_angle.radians, end_angle.radians),
mid_vertex,
end_vertex,
num_subdivisions,
vertex,
attributes,
output,
)?;
Ok(())
}
pub(crate) fn tessellate_empty_square_cap(
position: Point,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
vertex.position_on_path = position;
vertex.normal = vector(1.0, 1.0);
vertex.side = Side::Negative;
let a = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
vertex.normal = vector(1.0, -1.0);
vertex.side = Side::Positive;
let b = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
vertex.normal = vector(-1.0, -1.0);
vertex.side = Side::Positive;
let c = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
vertex.normal = vector(-1.0, 1.0);
vertex.side = Side::Negative;
let d = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
output.add_triangle(a, b, c);
output.add_triangle(a, c, d);
Ok(())
}
pub(crate) fn tessellate_empty_round_cap(
center: Point,
options: &StrokeOptions,
vertex: &mut StrokeVertexData,
attribute_store: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
let radius = vertex.half_width;
vertex.position_on_path = center;
vertex.normal = vector(-1.0, 0.0);
vertex.side = Side::Positive;
let left_id = output.add_stroke_vertex(StrokeVertex(vertex, attribute_store))?;
vertex.normal = vector(1.0, 0.0);
vertex.side = Side::Negative;
let right_id = output.add_stroke_vertex(StrokeVertex(vertex, attribute_store))?;
tessellate_round_cap(
center,
radius,
vector(-1.0, 0.0),
left_id,
right_id,
vector(0.0, 1.0),
options,
true,
vertex,
attribute_store,
output,
)?;
tessellate_round_cap(
center,
radius,
vector(1.0, 0.0),
right_id,
left_id,
vector(0.0, -1.0),
options,
false,
vertex,
attribute_store,
output,
)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn tessellate_arc(
angle: (f32, f32),
va: VertexId,
vb: VertexId,
num_recursions: u32,
vertex: &mut StrokeVertexData,
attributes: &dyn AttributeStore,
output: &mut dyn StrokeGeometryBuilder,
) -> Result<(), TessellationError> {
if num_recursions == 0 {
return Ok(());
}
let mid_angle = (angle.0 + angle.1) * 0.5;
let normal = vector(mid_angle.cos(), mid_angle.sin());
vertex.normal = normal;
let vertex_id = output.add_stroke_vertex(StrokeVertex(vertex, attributes))?;
output.add_triangle(va, vertex_id, vb);
tessellate_arc(
(angle.0, mid_angle),
va,
vertex_id,
num_recursions - 1,
vertex,
attributes,
output,
)?;
tessellate_arc(
(mid_angle, angle.1),
vertex_id,
vb,
num_recursions - 1,
vertex,
attributes,
output,
)
}
pub(crate) struct StrokeVertexData<'l> {
pub(crate) position_on_path: Point,
pub(crate) half_width: f32,
pub(crate) normal: Vector,
pub(crate) advancement: f32,
pub(crate) side: Side,
pub(crate) src: VertexSource,
pub(crate) buffer: &'l mut [f32],
pub(crate) buffer_is_valid: bool,
}
pub struct StrokeVertex<'a, 'b>(
pub(crate) &'b mut StrokeVertexData<'a>,
pub(crate) &'b dyn AttributeStore,
);
impl<'a, 'b> StrokeVertex<'a, 'b> {
#[inline]
pub fn position(&self) -> Point {
self.0.position_on_path + self.0.normal * self.0.half_width
}
#[inline]
pub fn normal(&self) -> Vector {
self.0.normal
}
#[inline]
pub fn position_on_path(&self) -> Point {
self.0.position_on_path
}
#[inline]
pub fn line_width(&self) -> f32 {
self.0.half_width * 2.0
}
#[inline]
pub fn advancement(&self) -> f32 {
self.0.advancement
}
#[inline]
pub fn side(&self) -> Side {
self.0.side
}
#[inline]
pub fn source(&self) -> VertexSource {
self.0.src
}
#[inline]
pub fn interpolated_attributes(&mut self) -> Attributes {
if self.0.buffer_is_valid {
return self.0.buffer;
}
match self.0.src {
VertexSource::Endpoint { id } => self.1.get(id),
VertexSource::Edge { from, to, t } => {
let a = self.1.get(from);
let b = self.1.get(to);
for i in 0..self.0.buffer.len() {
self.0.buffer[i] = a[i] * (1.0 - t) + b[i] * t;
}
self.0.buffer_is_valid = true;
self.0.buffer
}
}
}
}
pub(crate) fn circle_flattening_step(radius: f32, mut tolerance: f32) -> f32 {
tolerance = f32::min(tolerance, radius);
2.0 * ((radius - tolerance) / radius).acos()
}
fn flatten_quad<F>(curve: &QuadraticBezierSegment<f32>, tolerance: f32, cb: &mut F)
where
F: FnMut(Point, f32, bool),
{
if let Some(t_split) = find_sharp_turn(curve) {
let (before, after) = curve.split(t_split);
before.for_each_flattened_with_t(tolerance, &mut |line, t| {
let is_flattening_step = t.end != 1.0;
let t = t.end * t_split;
cb(line.to, t, is_flattening_step);
});
after.for_each_flattened_with_t(tolerance, &mut |line, t| {
let is_flattening_step = t.end != 1.0;
let t = t_split + t.end * (1.0 - t_split);
cb(line.to, t, is_flattening_step);
});
} else {
curve.for_each_flattened_with_t(tolerance, &mut |line, t| {
let is_flattening_step = t.end != 1.0;
cb(line.to, t.end, is_flattening_step);
});
}
}
#[cfg(test)]
use crate::geometry_builder::*;
#[cfg(test)]
use crate::path::Path;
#[cfg(test)]
fn test_path(path: PathSlice, options: &StrokeOptions, expected_triangle_count: Option<u32>) {
struct TestBuilder<'l> {
builder: SimpleBuffersBuilder<'l>,
}
impl<'l> GeometryBuilder for TestBuilder<'l> {
fn begin_geometry(&mut self) {
self.builder.begin_geometry();
}
fn end_geometry(&mut self) {
self.builder.end_geometry();
}
fn add_triangle(&mut self, a: VertexId, b: VertexId, c: VertexId) {
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
let pa = self.builder.buffers().vertices[a.0 as usize];
let pb = self.builder.buffers().vertices[b.0 as usize];
let pc = self.builder.buffers().vertices[c.0 as usize];
let threshold = -0.035; assert!((pa - pb).cross(pc - pb) >= threshold);
self.builder.add_triangle(a, b, c);
}
fn abort_geometry(&mut self) {
panic!();
}
}
impl<'l> StrokeGeometryBuilder for TestBuilder<'l> {
fn add_stroke_vertex(
&mut self,
attributes: StrokeVertex,
) -> Result<VertexId, GeometryBuilderError> {
assert!(!attributes.position().x.is_nan());
assert!(!attributes.position().y.is_nan());
assert!(!attributes.normal().x.is_nan());
assert!(!attributes.normal().y.is_nan());
assert_ne!(attributes.normal().square_length(), 0.0);
assert!(!attributes.advancement().is_nan());
self.builder.add_stroke_vertex(attributes)
}
}
let mut buffers: VertexBuffers<Point, u16> = VertexBuffers::new();
let mut tess = StrokeTessellator::new();
tess.tessellate_path(
path,
options,
&mut TestBuilder {
builder: simple_builder(&mut buffers),
},
)
.unwrap();
if let Some(triangles) = expected_triangle_count {
assert_eq!(
triangles,
buffers.indices.len() as u32 / 3,
"Unexpected number of triangles"
);
}
}
#[test]
fn test_square() {
let mut builder = Path::builder_with_attributes(1);
builder.begin(point(-1.0, 1.0), &[0.3]);
builder.line_to(point(1.0, 1.0), &[0.3]);
builder.line_to(point(1.0, -1.0), &[0.3]);
builder.line_to(point(-1.0, -1.0), &[0.3]);
builder.end(false);
builder.begin(point(-1.0, -1.0), &[0.3]);
builder.line_to(point(1.0, -1.0), &[0.3]);
builder.line_to(point(1.0, 1.0), &[0.3]);
builder.line_to(point(-1.0, 1.0), &[0.3]);
builder.end(false);
let path = builder.build();
let options = [
StrokeOptions::default().with_variable_line_width(0),
StrokeOptions::default(),
];
for options in options {
test_path(
path.as_slice(),
&options
.with_line_join(LineJoin::Miter)
.with_line_cap(LineCap::Butt),
Some(12),
);
test_path(
path.as_slice(),
&options
.with_line_join(LineJoin::Bevel)
.with_line_cap(LineCap::Square),
Some(16),
);
test_path(
path.as_slice(),
&options
.with_line_join(LineJoin::MiterClip)
.with_miter_limit(1.0)
.with_line_cap(LineCap::Round),
None,
);
test_path(
path.as_slice(),
&options
.with_tolerance(0.001)
.with_line_join(LineJoin::Round)
.with_line_cap(LineCap::Round),
None,
);
}
}
#[test]
fn test_empty_path() {
let path = Path::builder().build();
test_path(path.as_slice(), &StrokeOptions::default(), Some(0));
let path = Path::builder_with_attributes(1).build();
test_path(path.as_slice(), &StrokeOptions::default(), Some(0));
}
#[test]
fn test_empty_caps() {
let mut builder = Path::builder_with_attributes(1);
builder.begin(point(1.0, 0.0), &[1.0]);
builder.end(true);
builder.begin(point(2.0, 0.0), &[1.0]);
builder.line_to(point(2.0, 0.0), &[1.0]);
builder.end(false);
builder.begin(point(3.0, 0.0), &[1.0]);
builder.line_to(point(3.0, 0.0), &[1.0]);
builder.end(true);
builder.begin(point(3.0, 0.0), &[1.0]);
builder.line_to(point(3.0, 0.0), &[1.0]);
builder.line_to(point(3.0, 0.0), &[1.0]);
builder.line_to(point(3.0, 0.0), &[1.0]);
builder.end(true);
builder.begin(point(4.0, 0.0), &[1.0]);
builder.end(false);
let path = builder.build();
let options = [
StrokeOptions::default().with_variable_line_width(0),
StrokeOptions::default(),
];
for options in options {
test_path(
path.as_slice(),
&options.with_line_cap(LineCap::Butt),
Some(0),
);
test_path(
path.as_slice(),
&options.with_line_cap(LineCap::Square),
Some(8),
);
test_path(
path.as_slice(),
&options.with_line_cap(LineCap::Round),
None,
);
}
}
#[test]
fn test_too_many_vertices() {
use crate::extra::rust_logo::build_logo_path;
use crate::GeometryBuilder;
struct Builder {
max_vertices: u32,
}
impl GeometryBuilder for Builder {
fn begin_geometry(&mut self) {}
fn end_geometry(&mut self) {
panic!();
}
fn add_triangle(&mut self, a: VertexId, b: VertexId, c: VertexId) {
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
}
fn abort_geometry(&mut self) {}
}
impl StrokeGeometryBuilder for Builder {
fn add_stroke_vertex(&mut self, _: StrokeVertex) -> Result<VertexId, GeometryBuilderError> {
if self.max_vertices == 0 {
return Err(GeometryBuilderError::TooManyVertices);
}
self.max_vertices -= 1;
Ok(VertexId(self.max_vertices))
}
}
let mut path = Path::builder().with_svg();
build_logo_path(&mut path);
let path = path.build();
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.05);
assert_eq!(
tess.tessellate(&path, &options, &mut Builder { max_vertices: 0 }),
Err(TessellationError::GeometryBuilder(
GeometryBuilderError::TooManyVertices
)),
);
assert_eq!(
tess.tessellate(&path, &options, &mut Builder { max_vertices: 10 }),
Err(TessellationError::GeometryBuilder(
GeometryBuilderError::TooManyVertices
)),
);
assert_eq!(
tess.tessellate(&path, &options, &mut Builder { max_vertices: 100 }),
Err(TessellationError::GeometryBuilder(
GeometryBuilderError::TooManyVertices
)),
);
}
#[test]
fn stroke_vertex_source_01() {
let mut path = crate::path::Path::builder_with_attributes(1);
let a = path.begin(point(0.0, 0.0), &[1.0]);
let b = path.line_to(point(10.0, 10.0), &[2.0]);
let c = path.quadratic_bezier_to(point(10.0, 20.0), point(0.0, 20.0), &[3.0]);
path.end(true);
let path = path.build();
let mut tess = StrokeTessellator::new();
tess.tessellate_with_ids(
&mut path.id_iter(),
&path,
Some(&path),
&StrokeOptions::default().with_variable_line_width(0),
&mut CheckVertexSources {
next_vertex: 0,
a,
b,
c,
},
)
.unwrap();
struct CheckVertexSources {
next_vertex: u32,
a: EndpointId,
b: EndpointId,
c: EndpointId,
}
impl GeometryBuilder for CheckVertexSources {
fn add_triangle(&mut self, _: VertexId, _: VertexId, _: VertexId) {}
fn abort_geometry(&mut self) {}
}
fn eq(a: Point, b: Point) -> bool {
(a.x - b.x).abs() < 0.00001 && (a.y - b.y).abs() < 0.00001
}
impl StrokeGeometryBuilder for CheckVertexSources {
fn add_stroke_vertex(
&mut self,
mut attr: StrokeVertex,
) -> Result<VertexId, GeometryBuilderError> {
let pos = attr.position_on_path();
let src = attr.source();
if eq(pos, point(0.0, 0.0)) {
assert_eq!(src, VertexSource::Endpoint { id: self.a })
} else if eq(pos, point(10.0, 10.0)) {
assert_eq!(src, VertexSource::Endpoint { id: self.b })
} else if eq(pos, point(0.0, 20.0)) {
assert_eq!(src, VertexSource::Endpoint { id: self.c })
} else {
match src {
VertexSource::Edge { from, to, t } => {
assert_eq!(from, self.b);
assert_eq!(to, self.c);
assert!(t < 1.0);
assert!(t > 0.0);
}
_ => panic!("{:?} at {:?}", src, pos),
}
}
let vertex = attr.interpolated_attributes();
if eq(pos, point(0.0, 0.0)) {
assert_eq!(vertex, &[1.0])
} else if eq(pos, point(10.0, 10.0)) {
assert_eq!(vertex, &[2.0])
} else if eq(pos, point(0.0, 20.0)) {
assert_eq!(vertex, &[3.0])
} else {
assert_eq!(vertex.len(), 1);
assert!(vertex[0] > 2.0);
assert!(vertex[0] < 3.0);
}
let id = self.next_vertex;
self.next_vertex += 1;
Ok(VertexId(id))
}
}
}
#[test]
fn test_line_width() {
use crate::geom::euclid::approxeq::ApproxEq;
use crate::math::{point, Point};
let mut builder = crate::path::Path::builder();
builder.begin(point(0.0, 1.0));
builder.line_to(point(2.0, 1.0));
builder.end(false);
let path = builder.build();
let options = StrokeOptions::DEFAULT.with_line_width(2.0);
let mut geometry: VertexBuffers<Point, u16> = VertexBuffers::new();
StrokeTessellator::new()
.tessellate(
path.iter(),
&options,
&mut crate::geometry_builder::simple_builder(&mut geometry),
)
.unwrap();
for p in &geometry.vertices {
assert!(
p.approx_eq(&point(0.0, 0.0))
|| p.approx_eq(&point(0.0, 2.0))
|| p.approx_eq(&point(2.0, 0.0))
|| p.approx_eq(&point(2.0, 2.0))
);
}
}
trait IsNan {
fn is_nan(&self) -> bool;
}
impl IsNan for f32 {
fn is_nan(&self) -> bool {
f32::is_nan(*self)
}
}
impl IsNan for Point {
fn is_nan(&self) -> bool {
self.x.is_nan() || self.y.is_nan()
}
}
impl IsNan for Vector {
fn is_nan(&self) -> bool {
self.x.is_nan() || self.y.is_nan()
}
}
fn find_sharp_turn(curve: &QuadraticBezierSegment<f32>) -> Option<f32> {
let baseline = curve.to - curve.from;
let v = curve.ctrl - curve.from;
let n = vector(-baseline.y, baseline.x);
let v_dot_b = v.dot(baseline);
let v_dot_n = v.dot(n);
let long_axis = if (v_dot_b >= 0.0 && v_dot_b <= baseline.dot(baseline))
|| v_dot_n.abs() * 2.0 >= v_dot_b.abs()
{
if baseline.square_length() * 30.0 > v.square_length() {
return None;
}
v
} else {
baseline
};
let rot = crate::geom::euclid::Rotation2D::new(-long_axis.angle_from_x_axis());
let rotated = QuadraticBezierSegment {
from: point(0.0, 0.0),
ctrl: rot.transform_vector(v).to_point(),
to: rot.transform_vector(baseline).to_point(),
};
rotated.local_x_extremum_t()
}
#[test]
fn test_triangle_winding() {
use crate::math::{point, Point};
use crate::GeometryBuilder;
struct Builder {
vertices: Vec<Point>,
}
impl GeometryBuilder for Builder {
fn add_triangle(&mut self, a: VertexId, b: VertexId, c: VertexId) {
let a = self.vertices[a.to_usize()];
let b = self.vertices[b.to_usize()];
let c = self.vertices[c.to_usize()];
assert!((b - a).cross(c - b) <= 0.0);
}
}
impl StrokeGeometryBuilder for Builder {
fn add_stroke_vertex(&mut self, v: StrokeVertex) -> Result<VertexId, GeometryBuilderError> {
let id = VertexId(self.vertices.len() as u32);
self.vertices.push(v.position());
Ok(id)
}
}
let mut path = Path::builder().with_svg();
path.move_to(point(0.0, 0.0));
path.quadratic_bezier_to(point(100.0, 0.0), point(100.0, 100.0));
let path = path.build();
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.05);
tess.tessellate(
&path,
&options,
&mut Builder {
vertices: Vec::new(),
},
)
.unwrap();
}
#[test]
fn single_segment_closed() {
let mut path = Path::builder().with_svg();
path.move_to(point(0.0, 0.0));
path.line_to(point(100.0, 0.0));
let path = path.build();
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.05);
let mut output: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate(&path, &options, &mut simple_builder(&mut output))
.unwrap();
assert!(!output.indices.is_empty());
let mut path = Path::builder_with_attributes(1);
path.begin(point(0.0, 0.0), &[1.0]);
path.line_to(point(100.0, 0.0), &[1.0]);
path.end(false);
let path = path.build();
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.05);
let mut output: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate(&path, &options, &mut simple_builder(&mut output))
.unwrap();
assert!(!output.indices.is_empty());
}
#[test]
fn issue_819() {
let mut path = Path::builder();
path.begin(point(650.539978027344, 173.559997558594));
path.line_to(point(650.609985351562, 173.529998779297));
path.line_to(point(650.729980468750, 173.630004882812));
path.line_to(point(650.559997558594, 173.570007324219));
path.end(true);
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.1);
let mut output: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate(&path.build(), &options, &mut simple_builder(&mut output))
.unwrap();
let mut path = Path::builder_with_attributes(1);
path.begin(point(650.539978027344, 173.559997558594), &[0.0]);
path.line_to(point(650.609985351562, 173.529998779297), &[0.0]);
path.line_to(point(650.729980468750, 173.630004882812), &[0.0]);
path.line_to(point(650.559997558594, 173.570007324219), &[0.0]);
path.end(true);
let mut tess = StrokeTessellator::new();
let options = StrokeOptions::tolerance(0.1);
let mut output: VertexBuffers<Point, u16> = VertexBuffers::new();
tess.tessellate(&path.build(), &options, &mut simple_builder(&mut output))
.unwrap();
}
#[test]
fn issue_821() {
let mut tessellator = StrokeTessellator::new();
let options = StrokeOptions::default()
.with_tolerance(0.001)
.with_line_cap(LineCap::Round)
.with_variable_line_width(0);
let mut path = Path::builder_with_attributes(1);
path.begin(point(-45.192276, -69.800575), &[1.0]);
path.line_to(point(-45.164116, -69.84931), &[1.0]);
path.line_to(point(-45.135952, -69.90564), &[1.0]);
path.line_to(point(-45.10779, -69.93248), &[1.0]);
path.line_to(point(-45.052788, -69.96064), &[1.0]);
path.line_to(point(-45.026604, -69.898155), &[1.0]);
path.end(false);
let path = path.build();
let mut mesh = VertexBuffers::new();
let mut builder = simple_builder(&mut mesh);
tessellator
.tessellate_path(&path, &options, &mut builder)
.unwrap();
}
#[test]
fn issue_894() {
struct VariableWidthStrokeCtor;
impl StrokeVertexConstructor<[f32; 2]> for VariableWidthStrokeCtor {
fn new_vertex(&mut self, vertex: StrokeVertex) -> [f32; 2] {
vertex.position().to_array()
}
}
const STROKE_WIDTH: lyon_path::AttributeIndex = 0;
let mut builder = Path::builder_with_attributes(1);
builder.begin(point(435.72, 368.42), &[38.82]);
builder.line_to(point(433.53, 366.06), &[38.82]);
builder.quadratic_bezier_to(point(431.35, 363.70), point(430.22, 362.52), &[39.59]);
builder.quadratic_bezier_to(point(429.09, 361.34), point(429.05, 362.14), &[41.62]);
builder.line_to(point(429.00, 362.95), &[41.63]);
builder.end(false);
let path = builder.build();
let mut stroke_tessellator = StrokeTessellator::new();
let mut geometry: crate::VertexBuffers<[f32; 2], u16> = crate::VertexBuffers::new();
_ = stroke_tessellator.tessellate_path(
&path,
&StrokeOptions::tolerance(0.01)
.with_line_cap(LineCap::Round)
.with_variable_line_width(STROKE_WIDTH),
&mut BuffersBuilder::new(&mut geometry, VariableWidthStrokeCtor),
);
}