diff --git a/libraries/bezier-rs/src/bezier/core.rs b/libraries/bezier-rs/src/bezier/core.rs index 7f3886c2..29e67060 100644 --- a/libraries/bezier-rs/src/bezier/core.rs +++ b/libraries/bezier-rs/src/bezier/core.rs @@ -14,7 +14,7 @@ impl Bezier { } /// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points. - /// + /// pub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self { Bezier { start: p1, @@ -69,7 +69,7 @@ impl Bezier { /// - `t` - A representation of how far along the curve the provided point should occur at. The default value is 0.5. /// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively. /// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead. - /// + /// pub fn quadratic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: Option) -> Self { let t = t.unwrap_or(DEFAULT_T_VALUE); if t == 0. { diff --git a/libraries/bezier-rs/src/bezier/lookup.rs b/libraries/bezier-rs/src/bezier/lookup.rs index 379abcb3..3756cbb7 100644 --- a/libraries/bezier-rs/src/bezier/lookup.rs +++ b/libraries/bezier-rs/src/bezier/lookup.rs @@ -65,7 +65,7 @@ impl Bezier { /// Calculate the coordinates of the point `t` along the curve. /// Expects `t` to be within the inclusive range `[0, 1]`. - /// + /// pub fn evaluate(&self, t: TValue) -> DVec2 { let t = self.t_value_to_parametric(t); self.unrestricted_parametric_evaluate(t) @@ -73,7 +73,7 @@ impl Bezier { /// Return a selection of equidistant points on the bezier curve. /// If no value is provided for `steps`, then the function will default `steps` to be 10. - /// + /// pub fn compute_lookup_table(&self, steps: Option, tvalue_type: Option) -> Vec { let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE); let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric); @@ -91,7 +91,7 @@ impl Bezier { /// Return an approximation of the length of the bezier curve. /// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is 1000. - /// + /// pub fn length(&self, num_subdivisions: Option) -> f64 { match self.handles { BezierHandles::Linear => self.start.distance(self.end), @@ -118,7 +118,7 @@ impl Bezier { /// Returns the parametric `t`-value that corresponds to the closest point on the curve to the provided point. /// Uses a searching algorithm akin to binary search that can be customized using the optional [ProjectionOptions] struct. - /// + /// pub fn project(&self, point: DVec2, options: Option) -> f64 { let options = options.unwrap_or_default(); let ProjectionOptions { diff --git a/libraries/bezier-rs/src/bezier/solvers.rs b/libraries/bezier-rs/src/bezier/solvers.rs index 29e711b5..077f16cd 100644 --- a/libraries/bezier-rs/src/bezier/solvers.rs +++ b/libraries/bezier-rs/src/bezier/solvers.rs @@ -9,7 +9,7 @@ impl Bezier { /// Returns a list of lists of points representing the De Casteljau points for all iterations at the point `t` along the curve using De Casteljau's algorithm. /// The `i`th element of the list represents the set of points in the `i`th iteration. /// More information on the algorithm can be found in the [De Casteljau section](https://pomax.github.io/bezierinfo/#decasteljau) in Pomax's primer. - /// + /// pub fn de_casteljau_points(&self, t: TValue) -> Vec> { let t = self.t_value_to_parametric(t); let bezier_points = match self.handles { @@ -34,7 +34,7 @@ impl Bezier { /// Returns a [Bezier] representing the derivative of the original curve. /// - This function returns `None` for a linear segment. - /// + /// pub fn derivative(&self) -> Option { match self.handles { BezierHandles::Linear => None, @@ -61,7 +61,7 @@ impl Bezier { } /// Returns a normalized unit vector representing the tangent at the point `t` along the curve. - /// + /// pub fn tangent(&self, t: TValue) -> DVec2 { let t = self.t_value_to_parametric(t); let tangent = self.non_normalized_tangent(t); @@ -73,14 +73,14 @@ impl Bezier { } /// Returns a normalized unit vector representing the direction of the normal at the point `t` along the curve. - /// + /// pub fn normal(&self, t: TValue) -> DVec2 { self.tangent(t).perp() } /// Returns the curvature, a scalar value for the derivative at the point `t` along the curve. /// Curvature is 1 over the radius of a circle with an equivalent derivative. - /// + /// pub fn curvature(&self, t: TValue) -> f64 { let t = self.t_value_to_parametric(t); let (d, dd) = match &self.derivative() { @@ -127,7 +127,7 @@ impl Bezier { /// Returns two lists of `t`-values representing the local extrema of the `x` and `y` parametric curves respectively. /// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`. - /// + /// pub fn local_extrema(&self) -> [Vec; 2] { self.unrestricted_local_extrema() .into_iter() @@ -138,7 +138,7 @@ impl Bezier { } /// Return the min and max corners that represent the bounding box of the curve. - /// + /// pub fn bounding_box(&self) -> [DVec2; 2] { // Start by taking min/max of endpoints. let mut endpoints_min = self.start.min(self.end); @@ -199,7 +199,7 @@ impl Bezier { /// Returns list of parametric `t`-values representing the inflection points of the curve. /// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`. - /// + /// pub fn inflections(&self) -> Vec { self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::>() } @@ -256,7 +256,7 @@ impl Bezier { /// If the provided curve is linear, then zero intersection points will be returned along colinear segments. /// - `error` - For intersections where the provided bezier is non-linear, `error` defines the threshold for bounding boxes to be considered an intersection point. /// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order - /// + /// pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec { // TODO: Consider using the `intersections_between_vectors_of_curves` helper function here // Otherwise, use bounding box to determine intersections @@ -355,7 +355,7 @@ impl Bezier { // TODO: Use an `impl Iterator` return type instead of a `Vec` /// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point. /// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point. - /// + /// pub fn self_intersections(&self, error: Option) -> Vec<[f64; 2]> { if self.handles == BezierHandles::Linear || matches!(self.handles, BezierHandles::Quadratic { .. }) { return vec![]; @@ -387,7 +387,7 @@ impl Bezier { } /// Returns a list of parametric `t` values that correspond to the intersection points between the curve and a rectangle defined by opposite corners. - /// + /// pub fn rectangle_intersections(&self, corner1: DVec2, corner2: DVec2) -> Vec { [ Bezier::from_linear_coordinates(corner1.x, corner1.y, corner2.x, corner1.y), @@ -402,7 +402,7 @@ impl Bezier { /// Returns a cubic bezier which joins this with the provided bezier curve. /// The resulting path formed by the Bezier curves is continuous up to the first derivative. - /// + /// pub fn join(&self, other: &Bezier) -> Bezier { let handle1 = self.non_normalized_tangent(1.) / 3. + self.end; let handle2 = other.start - other.non_normalized_tangent(0.) / 3.; diff --git a/libraries/bezier-rs/src/bezier/transform.rs b/libraries/bezier-rs/src/bezier/transform.rs index 047b1bfa..2e7b7c3d 100644 --- a/libraries/bezier-rs/src/bezier/transform.rs +++ b/libraries/bezier-rs/src/bezier/transform.rs @@ -37,7 +37,7 @@ impl Bezier { } /// Returns the pair of Bezier curves that result from splitting the original curve at the point `t` along the curve. - /// + /// pub fn split(&self, t: TValue) -> [Bezier; 2] { let t = self.t_value_to_parametric(t); let split_point = self.evaluate(TValue::Parametric(t)); @@ -83,7 +83,7 @@ impl Bezier { /// Returns the Bezier curve representing the sub-curve between the two provided points. /// It will start at the point corresponding to the smaller of `t1` and `t2`, and end at the point corresponding to the larger of `t1` and `t2`. - /// + /// pub fn trim(&self, t1: TValue, t2: TValue) -> Bezier { let (mut t1, mut t2) = (self.t_value_to_parametric(t1), self.t_value_to_parametric(t2)); // If t1 is equal to t2, return a bezier comprised entirely of the same point @@ -122,7 +122,7 @@ impl Bezier { } /// Returns a Bezier curve that results from rotating the curve around the origin by the given angle (in radians). - /// + /// pub fn rotate(&self, angle: f64) -> Bezier { let rotation_matrix = DMat2::from_angle(angle); self.apply_transformation(|point| rotation_matrix.mul_vec2(point)) @@ -251,7 +251,7 @@ impl Bezier { /// The function takes the following parameter: /// - `step_size` - Dictates the granularity at which the function searches for reducible subcurves. The default value is `0.01`. /// A small granularity may increase the chance the function does not introduce gaps, but will increase computation time. - /// + /// pub fn reduce(&self, step_size: Option) -> Vec { self.reduced_curves_and_t_values(step_size).0 } @@ -355,7 +355,7 @@ impl Bezier { /// Offset takes the following parameter: /// - `distance` - The offset's distance from the curve. Positive values will offset the curve in the same direction as the endpoint normals, /// while negative values will offset in the opposite direction. - /// + /// pub fn offset(&self, distance: f64) -> Subpath { if self.is_point() { return Subpath::from_bezier(self); @@ -422,7 +422,7 @@ impl Bezier { /// The 'caps', the linear segments at opposite ends of the outline, intersect the original curve at the midpoint of the cap. /// Outline takes the following parameter: /// - `distance` - The outline's distance from the curve. - /// + /// pub fn outline(&self, distance: f64, cap: Cap) -> Subpath { let (pos_offset, neg_offset) = if self.is_point() { ( @@ -442,13 +442,13 @@ impl Bezier { /// Version of the `outline` function which draws the outline at the specified distances away from the curve. /// The outline begins `start_distance` away, and gradually move to being `end_distance` away. - /// + /// pub fn graduated_outline(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath { self.skewed_outline(start_distance, end_distance, end_distance, start_distance, cap) } /// Version of the `graduated_outline` function that allows for the 4 corners of the outline to be different distances away from the curve. - /// + /// pub fn skewed_outline(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath { let (pos_offset, neg_offset) = if self.is_point() { ( @@ -468,7 +468,7 @@ impl Bezier { /// Approximate a bezier curve with circular arcs. /// The algorithm can be customized using the [ArcsOptions] structure. - /// + /// pub fn arcs(&self, arcs_options: ArcsOptions) -> Vec { let ArcsOptions { strategy: maximize_arcs, diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 1b7b67f7..bccd0787 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -241,7 +241,7 @@ impl Subpath { } /// Construct a cubic spline from a list of points. - /// Based on https://mathworld.wolfram.com/CubicSpline.html + /// Based on . pub fn new_cubic_spline(points: Vec) -> Self { // Number of points = number of points to find handles for let len_points = points.len(); diff --git a/libraries/bezier-rs/src/subpath/lookup.rs b/libraries/bezier-rs/src/subpath/lookup.rs index f1e3a051..174a8f98 100644 --- a/libraries/bezier-rs/src/subpath/lookup.rs +++ b/libraries/bezier-rs/src/subpath/lookup.rs @@ -8,7 +8,7 @@ use glam::DVec2; impl Subpath { /// Return a selection of equidistant points on the bezier curve. /// If no value is provided for `steps`, then the function will default `steps` to be 10. - /// + /// pub fn compute_lookup_table(&self, steps: Option, tvalue_type: Option) -> Vec { let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE); let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric); @@ -26,7 +26,7 @@ impl Subpath { /// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`. /// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`. - /// + /// pub fn length(&self, num_subdivisions: Option) -> f64 { self.iter().fold(0., |accumulator, bezier| accumulator + bezier.length(num_subdivisions)) } @@ -97,7 +97,7 @@ impl Subpath { /// Returns the segment index and `t` value that corresponds to the closest point on the curve to the provided point. /// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure. - /// + /// pub fn project(&self, point: DVec2, options: Option) -> Option<(usize, f64)> { if self.is_empty() { return None; diff --git a/libraries/bezier-rs/src/subpath/manipulators.rs b/libraries/bezier-rs/src/subpath/manipulators.rs index 286f6850..011fad49 100644 --- a/libraries/bezier-rs/src/subpath/manipulators.rs +++ b/libraries/bezier-rs/src/subpath/manipulators.rs @@ -14,17 +14,17 @@ impl Subpath { self.closed = new_closed; } - /// Access a [ManipulatorGroup] from a [ManipulatorGroupId]. + /// Access a [ManipulatorGroup] from a ManipulatorGroupId. pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup> { self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id) } - /// Access a mutable [ManipulatorGroup] from a [ManipulatorGroupId]. + /// Access a mutable [ManipulatorGroup] from a ManipulatorGroupId. pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup> { self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id) } - /// Access the index of a [ManipulatorGroup] from a [ManipulatorGroupId]. + /// Access the index of a [ManipulatorGroup] from a ManipulatorGroupId. pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option { self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id) } diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index 72730ffe..5327aa11 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -9,7 +9,7 @@ use std::f64::consts::PI; impl Subpath { /// Calculate the point on the subpath based on the parametric `t`-value provided. /// Expects `t` to be within the inclusive range `[0, 1]`. - /// + /// pub fn evaluate(&self, t: SubpathTValue) -> DVec2 { let (segment_index, t) = self.t_value_to_parametric(t); self.get_segment(segment_index).unwrap().evaluate(TValue::Parametric(t)) @@ -23,7 +23,7 @@ impl Subpath { /// - `error`: an optional f64 value to provide an error bound /// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order. /// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two. - /// + /// pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> { self.iter() .enumerate() @@ -35,7 +35,7 @@ impl Subpath { /// This function expects the following: /// - other: a [Bezier] curve to check intersections against /// - error: an optional f64 value to provide an error bound - /// + /// pub fn subpath_intersections(&self, other: &Subpath, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> { let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect(); intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap()); @@ -48,7 +48,7 @@ impl Subpath { /// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two /// /// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out. - /// + /// pub fn self_intersections(&self, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> { let mut intersections_vec = Vec::new(); let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE); @@ -69,14 +69,14 @@ impl Subpath { } /// Returns a normalized unit vector representing the tangent on the subpath based on the parametric `t`-value provided. - /// + /// pub fn tangent(&self, t: SubpathTValue) -> DVec2 { let (segment_index, t) = self.t_value_to_parametric(t); self.get_segment(segment_index).unwrap().tangent(TValue::Parametric(t)) } /// Returns a normalized unit vector representing the direction of the normal on the subpath based on the parametric `t`-value provided. - /// + /// pub fn normal(&self, t: SubpathTValue) -> DVec2 { let (segment_index, t) = self.t_value_to_parametric(t); self.get_segment(segment_index).unwrap().normal(TValue::Parametric(t)) @@ -84,7 +84,7 @@ impl Subpath { /// Returns two lists of `t`-values representing the local extrema of the `x` and `y` parametric subpaths respectively. /// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`. - /// + /// pub fn local_extrema(&self) -> [Vec; 2] { let number_of_curves = self.len_segments() as f64; @@ -99,7 +99,7 @@ impl Subpath { } /// Return the min and max corners that represent the bounding box of the subpath. - /// + /// pub fn bounding_box(&self) -> Option<[DVec2; 2]> { self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) } @@ -138,7 +138,15 @@ impl Subpath { } /// Returns the manipulator point that is needed for a miter join if it is possible. - pub(crate) fn miter_line_join(&self, other: &Subpath) -> Option> { + /// - `miter_limit`: Defines a limit for the ratio between the miter length and the stroke width. + /// Alternatively, this can be interpreted as limiting the angle that the miter can form. + /// When the limit is exceeded, no manipulator group will be returned. + /// This value should be at least 1. If not, the default of 4 will be used. + pub(crate) fn miter_line_join(&self, other: &Subpath, miter_limit: Option) -> Option> { + let miter_limit = match miter_limit { + Some(miter_limit) if miter_limit >= 1. => miter_limit, + _ => 4., + }; let in_segment = self.get_segment(self.len_segments() - 1).unwrap(); let out_segment = other.get_segment(0).unwrap(); let in_tangent = in_segment.tangent(TValue::Parametric(1.)); @@ -151,9 +159,13 @@ impl Subpath { if !normalized_in_tangent.abs_diff_eq(normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) && !normalized_in_tangent.abs_diff_eq(-normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) { let intersection = line_intersection(in_segment.end(), in_tangent, out_segment.start(), out_tangent); + let start_to_intersection = intersection - in_segment.end(); + let intersection_to_end = out_segment.start() - intersection; + // Draw the miter join if the intersection occurs in the correct direction with respect to the path - if (intersection - in_segment.end()).normalize().abs_diff_eq(in_tangent, MAX_ABSOLUTE_DIFFERENCE) - && (out_segment.start() - intersection).normalize().abs_diff_eq(out_tangent, MAX_ABSOLUTE_DIFFERENCE) + if start_to_intersection.normalize().abs_diff_eq(in_tangent, MAX_ABSOLUTE_DIFFERENCE) + && intersection_to_end.normalize().abs_diff_eq(out_tangent, MAX_ABSOLUTE_DIFFERENCE) + && miter_limit >= 1. / (start_to_intersection.angle_between(-intersection_to_end).abs() / 2.).sin() { return Some(ManipulatorGroup { anchor: intersection, @@ -225,7 +237,7 @@ impl Subpath { /// Returns the curvature, a scalar value for the derivative at the point `t` along the subpath. /// Curvature is 1 over the radius of a circle with an equivalent derivative. - /// + /// pub fn curvature(&self, t: SubpathTValue) -> f64 { let (segment_index, t) = self.t_value_to_parametric(t); self.get_segment(segment_index).unwrap().curvature(TValue::Parametric(t)) diff --git a/libraries/bezier-rs/src/subpath/transform.rs b/libraries/bezier-rs/src/subpath/transform.rs index d47507d1..2e802272 100644 --- a/libraries/bezier-rs/src/subpath/transform.rs +++ b/libraries/bezier-rs/src/subpath/transform.rs @@ -22,7 +22,7 @@ impl Subpath { /// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`. /// If the original Subpath was closed, a single open Subpath will be returned. /// If the original Subpath was open, two open Subpaths will be returned. - /// + /// pub fn split(&self, t: SubpathTValue) -> (Subpath, Option>) { let (segment_index, t) = self.t_value_to_parametric(t); let curve = self.get_segment(segment_index).unwrap(); @@ -126,7 +126,7 @@ impl Subpath { /// The resulting Subpath will wind from the given `t1` to `t2`. /// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`. /// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`. - /// + /// pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath { // Return a clone of the Subpath if it is not long enough to be a valid Bezier if self.manipulator_groups.is_empty() { @@ -334,7 +334,7 @@ impl Subpath { } /// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians). - /// + /// pub fn rotate(&self, angle: f64) -> Subpath { let mut rotated_subpath = self.clone(); @@ -411,8 +411,8 @@ impl Subpath { drop_common_point[j] = false; match join { Join::Bevel => {} - Join::Miter => { - let miter_manipulator_group = subpaths[i].miter_line_join(&subpaths[j]); + Join::Miter(miter_limit) => { + let miter_manipulator_group = subpaths[i].miter_line_join(&subpaths[j], miter_limit); if let Some(miter_manipulator_group) = miter_manipulator_group { subpaths[i].manipulator_groups.push(miter_manipulator_group); } @@ -448,9 +448,9 @@ impl Subpath { drop_common_point[0] = false; match join { Join::Bevel => {} - Join::Miter => { + Join::Miter(miter_limit) => { let last_subpath_index = subpaths.len() - 1; - let miter_manipulator_group = subpaths[last_subpath_index].miter_line_join(&subpaths[0]); + let miter_manipulator_group = subpaths[last_subpath_index].miter_line_join(&subpaths[0], miter_limit); if let Some(miter_manipulator_group) = miter_manipulator_group { subpaths[last_subpath_index].manipulator_groups.push(miter_manipulator_group); } @@ -526,7 +526,7 @@ impl Subpath { /// an approximate outline around the subpath at a specified distance from the curve. Outline takes the following parameters: /// - `distance` - The outline's distance from the curve. /// - `join` - The join type used to cap the endpoints of open bezier curves, and join successive subpath segments. - /// + /// pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath, Option>) { let is_point = self.is_point(); let (pos_offset, neg_offset) = if is_point { diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 62b1c5dc..cc050a81 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -36,17 +36,25 @@ pub enum SubpathTValue { } #[derive(Copy, Clone)] -/// Enum to represent the join type between subpaths. -/// As defined in SVG: https://www.w3.org/TR/SVG2/painting.html#LineJoin. +/// Represents the shape of the join between two segments of a path which meet at an angle. +/// Bevel provides a flat connection, Miter provides a sharp connection, and Round provides a rounded connection. +/// As defined in SVG: . pub enum Join { + /// The join is a straight line between the end points of the offset path sides from the two connecting segments. Bevel, - Miter, + /// Optional f64 is the miter limit, which defaults to 4 if `None` or a value less than 1 is provided. + /// The miter limit is used to prevent highly sharp angles from resulting in excessively long miter joins. + /// If the miter limit is exceeded, the join will be converted to a bevel join. + /// The value is the ratio of the miter length to the stroke width. + /// When that ratio is greater than the miter limit, a bevel join is used instead. + Miter(Option), + /// The join is a circular arc between the end points of the offset path sides from the two connecting segments. Round, } #[derive(Copy, Clone)] /// Enum to represent the cap type at the ends of an outline -/// As defined in SVG: https://www.w3.org/TR/SVG2/painting.html#LineCaps. +/// As defined in SVG: . pub enum Cap { Butt, Round, diff --git a/website/other/bezier-rs-demos/src/features/subpath-features.ts b/website/other/bezier-rs-demos/src/features/subpath-features.ts index b64f05a4..fa49c8ad 100644 --- a/website/other/bezier-rs-demos/src/features/subpath-features.ts +++ b/website/other/bezier-rs-demos/src/features/subpath-features.ts @@ -126,7 +126,7 @@ const subpathFeatures = { }, offset: { name: "Offset", - callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.offset(options.distance, options.join), + callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.offset(options.distance, options.join, options.miter_limit), inputOptions: [ { variable: "distance", @@ -136,11 +136,18 @@ const subpathFeatures = { default: 10, }, joinOptions, + { + variable: "join: Miter - limit", + min: 1, + max: 10, + step: 0.25, + default: 4, + }, ], }, outline: { name: "Outline", - callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.outline(options.distance, options.join, options.cap), + callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.outline(options.distance, options.join, options.cap, options.miter_limit), inputOptions: [ { variable: "distance", @@ -150,6 +157,13 @@ const subpathFeatures = { default: 10, }, joinOptions, + { + variable: "join: Miter - limit", + min: 1, + max: 10, + step: 0.25, + default: 4, + }, { ...capOptions, isDisabledForClosed: true }, ], }, diff --git a/website/other/bezier-rs-demos/src/utils/options.ts b/website/other/bezier-rs-demos/src/utils/options.ts index 0a3d03e2..26d592b4 100644 --- a/website/other/bezier-rs-demos/src/utils/options.ts +++ b/website/other/bezier-rs-demos/src/utils/options.ts @@ -1,4 +1,4 @@ -import { BEZIER_T_VALUE_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types"; +import { BEZIER_T_VALUE_VARIANTS, CAP_VARIANTS, JOIN_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types"; export const tSliderOptions = { min: 0, @@ -50,12 +50,12 @@ export const joinOptions = { variable: "join", default: 0, inputType: "dropdown", - options: ["Bevel", "Miter", "Round"], + options: JOIN_VARIANTS, }; export const capOptions = { variable: "cap", default: 0, inputType: "dropdown", - options: ["Butt", "Round", "Square"], + options: CAP_VARIANTS, }; diff --git a/website/other/bezier-rs-demos/src/utils/types.ts b/website/other/bezier-rs-demos/src/utils/types.ts index b1c5fb7f..de40fa9a 100644 --- a/website/other/bezier-rs-demos/src/utils/types.ts +++ b/website/other/bezier-rs-demos/src/utils/types.ts @@ -95,3 +95,6 @@ export interface DemoPane extends HTMLElement { export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const; export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const; + +export const CAP_VARIANTS = ["Butt", "Round", "Square"] as const; +export const JOIN_VARIANTS = ["Bevel", "Miter", "Round"] as const; diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index 3706f8fd..cca7e2c2 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -441,8 +441,8 @@ impl WasmSubpath { wrap_svg_tag(format!("{}{}", self.to_default_svg(), trimmed_subpath_svg)) } - pub fn offset(&self, distance: f64, join: i32) -> String { - let join = parse_join(join); + pub fn offset(&self, distance: f64, join: i32, miter_limit: f64) -> String { + let join = parse_join(join, miter_limit); let offset_subpath = self.0.offset(distance, join); let mut offset_svg = String::new(); @@ -451,8 +451,8 @@ impl WasmSubpath { wrap_svg_tag(format!("{}{offset_svg}", self.to_default_svg())) } - pub fn outline(&self, distance: f64, join: i32, cap: i32) -> String { - let join = parse_join(join); + pub fn outline(&self, distance: f64, join: i32, cap: i32, miter_limit: f64) -> String { + let join = parse_join(join, miter_limit); let cap = parse_cap(cap); let (outline_piece1, outline_piece2) = self.0.outline(distance, join, cap); diff --git a/website/other/bezier-rs-demos/wasm/src/utils.rs b/website/other/bezier-rs-demos/wasm/src/utils.rs index 58dc1329..00f21daf 100644 --- a/website/other/bezier-rs-demos/wasm/src/utils.rs +++ b/website/other/bezier-rs-demos/wasm/src/utils.rs @@ -1,9 +1,9 @@ use bezier_rs::{Cap, Join}; -pub fn parse_join(join: i32) -> Join { +pub fn parse_join(join: i32, miter_limit: f64) -> Join { match join { 0 => Join::Bevel, - 1 => Join::Miter, + 1 => Join::Miter(Some(miter_limit)), 2 => Join::Round, _ => panic!("Unexpected Join value: '{}'", join), }