mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Refactor the Centroid node and Subpath struct and methods to use Kurbo, eliminating all remaining usages of Bezier-rs (#3036)
* define Subpath struct in gcore and refactor node-graph * Refactor few methods * refactoring worked! * refactor centoid area and length * remove unused * cleanup * fix pathseg_points function * fix tranforming segments * fix segment intersection * refactor to_path_segments fn in gpath-bool crate * refactor gcraft * add bezier-rs dep * Code review the editor directory * use path-bool for solving roots * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use super::intersection::bezpath_intersections;
|
||||
use super::poisson_disk::poisson_disk_sample;
|
||||
use super::util::segment_tangent;
|
||||
use super::util::pathseg_tangent;
|
||||
use crate::math::polynomial::pathseg_to_parametric_polynomial;
|
||||
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
|
||||
use glam::{DMat2, DVec2};
|
||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
|
||||
use kurbo::common::{solve_cubic, solve_quadratic};
|
||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
||||
use std::f64::consts::{FRAC_PI_2, PI};
|
||||
|
||||
/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].
|
||||
@@ -187,6 +189,82 @@ pub enum TValue {
|
||||
Euclidean(f64),
|
||||
}
|
||||
|
||||
/// Default LUT step size in `compute_lookup_table` function.
|
||||
pub const DEFAULT_LUT_STEP_SIZE: usize = 10;
|
||||
|
||||
/// 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 pathseg_compute_lookup_table(segment: PathSeg, steps: Option<usize>, eucliean: bool) -> impl Iterator<Item = DVec2> {
|
||||
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
||||
|
||||
(0..=steps).map(move |t| {
|
||||
let tvalue = if eucliean {
|
||||
TValue::Euclidean(t as f64 / steps as f64)
|
||||
} else {
|
||||
TValue::Parametric(t as f64 / steps as f64)
|
||||
};
|
||||
let t = eval_pathseg(segment, tvalue);
|
||||
point_to_dvec2(segment.eval(t))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an `Iterator` containing all possible parametric `t`-values at the given `x`-coordinate.
|
||||
pub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Item = f64> + use<> {
|
||||
match segment {
|
||||
PathSeg::Line(Line { p0, p1 }) => {
|
||||
// If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots
|
||||
let a = p1.x - p0.x;
|
||||
let b = p0.x - x;
|
||||
|
||||
// Find the roots of the linear equation `ax + b`.
|
||||
// There exist roots when `a` is not 0
|
||||
if a.abs() > MAX_ABSOLUTE_DIFFERENCE { [Some(-b / a), None, None] } else { [None; 3] }
|
||||
}
|
||||
PathSeg::Quad(QuadBez { p0, p1, p2 }) => {
|
||||
let a = p2.x - 2.0 * p1.x + p0.x;
|
||||
let b = 2.0 * (p1.x - p0.x);
|
||||
let c = p0.x - x;
|
||||
let r = solve_quadratic(c, b, a);
|
||||
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), None]
|
||||
}
|
||||
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => {
|
||||
let a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x;
|
||||
let b = 3.0 * (p2.x - 2.0 * p1.x + p0.x);
|
||||
let c = 3.0 * (p1.x - p0.x);
|
||||
let d = p0.x - x;
|
||||
let r = solve_cubic(d, c, b, a);
|
||||
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), r.get(2).map(|t| *t)]
|
||||
}
|
||||
}
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|&t| (0.0..1.).contains(&t))
|
||||
}
|
||||
|
||||
/// Find the `t`-value(s) such that the normal(s) at `t` pass through the specified point.
|
||||
pub fn pathseg_normals_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
|
||||
// We solve deriv(t) dot (self(t) - point) = 0.
|
||||
let (mut x, mut y) = pathseg_to_parametric_polynomial(segment);
|
||||
let x = x.coefficients_mut();
|
||||
let y = y.coefficients_mut();
|
||||
x[0] -= point.x;
|
||||
y[0] -= point.y;
|
||||
let poly = poly_cool::Poly::new([
|
||||
x[0] * x[1] + y[0] * y[1],
|
||||
x[1] * x[1] + y[1] * y[1] + 2. * (x[0] * x[2] + y[0] * y[2]),
|
||||
3. * (x[2] * x[1] + y[2] * y[1]) + 3. * (x[0] * x[3] + y[0] * y[3]),
|
||||
4. * (x[3] * x[1] + y[3] * y[1]) + 2. * (x[2] * x[2] + y[2] * y[2]),
|
||||
5. * (x[3] * x[2] + y[3] * y[2]),
|
||||
3. * (x[3] * x[3] + y[3] * y[3]),
|
||||
]);
|
||||
poly.roots_between(0., 1., 1e-8)
|
||||
}
|
||||
|
||||
/// Find the `t`-value(s) such that the tangent(s) at `t` pass through the given point.
|
||||
pub fn pathseg_tangents_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
|
||||
segment.to_cubic().tangents_to_point(point).to_vec()
|
||||
}
|
||||
|
||||
/// Return the subsegment for the given [TValue] range. Returns None if parametric value of `t1` is greater than `t2`.
|
||||
pub fn trim_pathseg(segment: PathSeg, t1: TValue, t2: TValue) -> Option<PathSeg> {
|
||||
let t1 = eval_pathseg(segment, t1);
|
||||
@@ -202,6 +280,68 @@ pub fn eval_pathseg(segment: PathSeg, t_value: TValue) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an approximation of the length centroid, together with the length, of the bezier curve.
|
||||
///
|
||||
/// The length centroid is the center of mass for the arc length of the Bezier segment.
|
||||
/// An infinitely thin wire forming the Bezier segment's shape would balance at this point.
|
||||
///
|
||||
/// - `accuracy` is used to approximate the curve.
|
||||
pub(crate) fn pathseg_length_centroid_and_length(segment: PathSeg, accuracy: Option<f64>) -> (Vec2, f64) {
|
||||
match segment {
|
||||
PathSeg::Line(line) => ((line.start().to_vec2() + line.end().to_vec2()) / 2., (line.start().to_vec2() - line.end().to_vec2()).length()),
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let QuadBez { p0, p1, p2 } = quad_bez;
|
||||
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
||||
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
||||
let lower = (a2 - a1).length();
|
||||
let upper = (a1 - a0).length() + (a2 - a1).length();
|
||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||
let length = (lower + upper) / 2.;
|
||||
return (length, length * (a0 + a1 + a2) / 3.);
|
||||
}
|
||||
|
||||
let b1 = 0.5 * (a0 + a1);
|
||||
let c1 = 0.5 * (a1 + a2);
|
||||
let b2 = 0.5 * (b1 + c1);
|
||||
|
||||
let (length1, centroid_part1) = recurse(a0, b1, b2, 0.5 * accuracy, level + 1);
|
||||
let (length2, centroid_part2) = recurse(b2, c1, a2, 0.5 * accuracy, level + 1);
|
||||
(length1 + length2, centroid_part1 + centroid_part2)
|
||||
}
|
||||
|
||||
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), accuracy.unwrap_or_default(), 0);
|
||||
(centroid_parts / length, length)
|
||||
}
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let CubicBez { p0, p1, p2, p3 } = cubic_bez;
|
||||
|
||||
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
||||
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, a3: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
||||
let lower = (a3 - a0).length();
|
||||
let upper = (a1 - a0).length() + (a2 - a1).length() + (a3 - a2).length();
|
||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||
let length = (lower + upper) / 2.;
|
||||
return (length, length * (a0 + a1 + a2 + a3) / 4.);
|
||||
}
|
||||
|
||||
let b1 = 0.5 * (a0 + a1);
|
||||
let t0 = 0.5 * (a1 + a2);
|
||||
let c1 = 0.5 * (a2 + a3);
|
||||
let b2 = 0.5 * (b1 + t0);
|
||||
let c2 = 0.5 * (t0 + c1);
|
||||
let b3 = 0.5 * (b2 + c2);
|
||||
|
||||
let (length1, centroid_part1) = recurse(a0, b1, b2, b3, 0.5 * accuracy, level + 1);
|
||||
let (length2, centroid_part2) = recurse(b3, c2, c1, a3, 0.5 * accuracy, level + 1);
|
||||
(length1 + length2, centroid_part1 + centroid_part2)
|
||||
}
|
||||
|
||||
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), p3.to_vec2(), accuracy.unwrap_or_default(), 0);
|
||||
(centroid_parts / length, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the t value of point on the given path segment i.e fractional distance along the segment's total length.
|
||||
/// It uses a binary search to find the value `t` such that the ratio `length_up_to_t / total_length` approximates the input `distance`.
|
||||
pub fn eval_pathseg_euclidean(segment: PathSeg, distance: f64, accuracy: f64) -> f64 {
|
||||
@@ -392,8 +532,8 @@ pub fn miter_line_join(bezpath1: &BezPath, bezpath2: &BezPath, miter_limit: Opti
|
||||
let in_segment = bezpath1.segments().last()?;
|
||||
let out_segment = bezpath2.segments().next()?;
|
||||
|
||||
let in_tangent = segment_tangent(in_segment, 1.);
|
||||
let out_tangent = segment_tangent(out_segment, 0.);
|
||||
let in_tangent = pathseg_tangent(in_segment, 1.);
|
||||
let out_tangent = pathseg_tangent(out_segment, 0.);
|
||||
|
||||
if in_tangent == DVec2::ZERO || out_tangent == DVec2::ZERO {
|
||||
// Avoid panic from normalizing zero vectors
|
||||
@@ -454,7 +594,7 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) ->
|
||||
let center_to_left = left - center;
|
||||
|
||||
let in_segment = bezpath1.segments().last();
|
||||
let in_tangent = in_segment.map(|in_segment| segment_tangent(in_segment, 1.));
|
||||
let in_tangent = in_segment.map(|in_segment| pathseg_tangent(in_segment, 1.));
|
||||
|
||||
let mut angle = center_to_right.angle_to(center_to_left) / 2.;
|
||||
let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);
|
||||
|
||||
@@ -105,8 +105,8 @@ mod test {
|
||||
use super::*;
|
||||
use crate::Node;
|
||||
use crate::extract_xy::{ExtractXyNode, XY};
|
||||
use crate::subpath::Subpath;
|
||||
use crate::vector::Vector;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use std::pin::Pin;
|
||||
|
||||
|
||||
@@ -45,12 +45,26 @@ pub fn segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Opt
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subsegment_intersections(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);
|
||||
|
||||
match (segment1, segment2) {
|
||||
(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),
|
||||
(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),
|
||||
(segment1, segment2) => {
|
||||
let mut intersections = Vec::new();
|
||||
segment_intersections_inner(segment1, min_t1, max_t1, segment2, min_t2, max_t2, accuracy, &mut intersections);
|
||||
intersections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [https://pomax.github.io/bezierinfo/#curveintersection] to find intersection between two Bezier segments
|
||||
/// by splitting the segment recursively until the size of the subsegment's bounding box is smaller than the accuracy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: f64, intersections: &mut Vec<(f64, f64)>) {
|
||||
let bbox1 = segment1.bounding_box();
|
||||
let bbox2 = segment2.bounding_box();
|
||||
let bbox1 = segment1.subsegment(min_t1..max_t1).bounding_box();
|
||||
let bbox2 = segment2.subsegment(min_t2..max_t2).bounding_box();
|
||||
|
||||
let mid_t1 = (min_t1 + max_t1) / 2.;
|
||||
let mid_t2 = (min_t2 + max_t2) / 2.;
|
||||
@@ -58,7 +72,7 @@ fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segm
|
||||
// Check if the bounding boxes overlap
|
||||
if bbox1.overlaps(bbox2) {
|
||||
// If bounding boxes overlap and they are small enough, we have found an intersection
|
||||
if bbox1.width() < accuracy && bbox1.height() < accuracy && bbox2.width() < accuracy && bbox2.height() < accuracy {
|
||||
if bbox1.width().abs() < accuracy && bbox1.height().abs() < accuracy && bbox2.width().abs() < accuracy && bbox2.height().abs() < accuracy {
|
||||
// Use the middle `t` value, append the corresponding `t` value
|
||||
intersections.push((mid_t1, mid_t2));
|
||||
return;
|
||||
@@ -125,6 +139,66 @@ pub fn filtered_all_segment_intersections(segment1: PathSeg, segment2: PathSeg,
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function to compute intersections between lists of subcurves.
|
||||
/// This function uses the algorithm implemented in `intersections_between_subcurves`.
|
||||
fn intersections_between_vectors_of_path_segments(subcurves1: &[(f64, f64, PathSeg)], subcurves2: &[(f64, f64, PathSeg)], accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let segment_pairs = subcurves1.iter().flat_map(move |(t11, t12, curve1)| {
|
||||
subcurves2
|
||||
.iter()
|
||||
.filter_map(move |(t21, t22, curve2)| curve1.bounding_box().overlaps(curve2.bounding_box()).then_some((t11, t12, curve1, t21, t22, curve2)))
|
||||
});
|
||||
|
||||
segment_pairs
|
||||
.flat_map(|(&t11, &t12, &curve1, &t21, &t22, &curve2)| subsegment_intersections(curve1, t11, t12, curve2, t21, t22, accuracy))
|
||||
.collect::<Vec<(f64, f64)>>()
|
||||
}
|
||||
|
||||
fn pathseg_self_intersection(segment: PathSeg, accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let cubic_bez = match segment {
|
||||
PathSeg::Line(_) | PathSeg::Quad(_) => return vec![],
|
||||
PathSeg::Cubic(cubic_bez) => cubic_bez,
|
||||
};
|
||||
|
||||
// Get 2 copies of the reduced curves
|
||||
let quads1 = cubic_bez.to_quads(DEFAULT_ACCURACY).map(|(t1, t2, quad_bez)| (t1, t2, PathSeg::Quad(quad_bez))).collect::<Vec<_>>();
|
||||
let quads2 = quads1.clone();
|
||||
|
||||
let num_curves = quads1.len();
|
||||
|
||||
// Adjacent reduced curves cannot intersect
|
||||
if num_curves <= 2 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// For each curve, look for intersections with every curve that is at least 2 indices away
|
||||
quads1
|
||||
.iter()
|
||||
.take(num_curves - 2)
|
||||
.enumerate()
|
||||
.flat_map(|(index, &subsegment)| intersections_between_vectors_of_path_segments(&[subsegment], &quads2[index + 2..], accuracy))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// If the difference between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.
|
||||
/// - `error` - For intersections with non-linear beziers, `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 pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let mut intersection_t_values = pathseg_self_intersection(segment, accuracy);
|
||||
intersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());
|
||||
|
||||
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
||||
if !accumulator.is_empty()
|
||||
&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
{
|
||||
accumulator.pop();
|
||||
}
|
||||
accumulator.push(*t);
|
||||
accumulator
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use glam::DVec2;
|
||||
use kurbo::{ParamCurve, ParamCurveDeriv, PathSeg};
|
||||
|
||||
pub fn segment_tangent(segment: PathSeg, t: f64) -> DVec2 {
|
||||
pub fn pathseg_tangent(segment: PathSeg, t: f64) -> DVec2 {
|
||||
// NOTE: .deriv() method gives inaccurate result when it is 1.
|
||||
let t = if t == 1. { 1. - f64::EPSILON } else { t };
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use super::algorithms::intersection::filtered_segment_intersections;
|
||||
use super::misc::dvec2_to_point;
|
||||
use crate::math::math_ext::QuadExt;
|
||||
use crate::math::quad::Quad;
|
||||
use crate::subpath::Subpath;
|
||||
use crate::vector::PointId;
|
||||
use bezier_rs::Subpath;
|
||||
use crate::vector::misc::point_to_dvec2;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use kurbo::{Affine, ParamCurve, PathSeg, Point, Shape};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FreePoint {
|
||||
@@ -99,7 +103,7 @@ impl ClickTarget {
|
||||
}
|
||||
|
||||
/// Does the click target intersect the path
|
||||
pub fn intersect_path<It: Iterator<Item = bezier_rs::Bezier>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
|
||||
pub fn intersect_path<It: Iterator<Item = PathSeg>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
|
||||
// Check if the matrix is not invertible
|
||||
let mut layer_transform = layer_transform;
|
||||
if layer_transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
@@ -107,25 +111,25 @@ impl ClickTarget {
|
||||
}
|
||||
|
||||
let inverse = layer_transform.inverse();
|
||||
let mut bezier_iter = || bezier_iter().map(|bezier| bezier.apply_transformation(|point| inverse.transform_point2(point)));
|
||||
let mut bezier_iter = || bezier_iter().map(|bezier| Affine::new(inverse.to_cols_array()) * bezier);
|
||||
|
||||
match self.target_type() {
|
||||
ClickTargetType::Subpath(subpath) => {
|
||||
// Check if outlines intersect
|
||||
let outline_intersects = |path_segment: bezier_rs::Bezier| bezier_iter().any(|line| !path_segment.intersections(&line, None, None).is_empty());
|
||||
let outline_intersects = |path_segment: PathSeg| bezier_iter().any(|line| !filtered_segment_intersections(path_segment, line, None, None).is_empty());
|
||||
if subpath.iter().any(outline_intersects) {
|
||||
return true;
|
||||
}
|
||||
// Check if selection is entirely within the shape
|
||||
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(bezier.start)) {
|
||||
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(point_to_dvec2(bezier.start()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if shape is entirely within selection
|
||||
let any_point_from_subpath = subpath.manipulator_groups().first().map(|manipulators| manipulators.anchor);
|
||||
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(shape_point)).sum::<i32>() != 0)
|
||||
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(Point::new(shape_point.x, shape_point.y))).sum::<i32>() != 0)
|
||||
}
|
||||
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: bezier_rs::Bezier| bezier.winding(point.position)).sum::<i32>() != 0,
|
||||
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: PathSeg| bezier.winding(dvec2_to_point(point.position))).sum::<i32>() != 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +148,7 @@ impl ClickTarget {
|
||||
// Allows for selecting lines
|
||||
// TODO: actual intersection of stroke
|
||||
let inflated_quad = Quad::from_box(target_bounds);
|
||||
self.intersect_path(|| inflated_quad.bezier_lines(), layer_transform)
|
||||
self.intersect_path(|| inflated_quad.to_lines(), layer_transform)
|
||||
}
|
||||
|
||||
/// Does the click target intersect the point (not accounting for stroke size)
|
||||
|
||||
@@ -2,10 +2,10 @@ use super::misc::{ArcType, AsU64, GridType};
|
||||
use super::{PointId, SegmentId, StrokeId};
|
||||
use crate::Ctx;
|
||||
use crate::registry::types::{Angle, PixelSize};
|
||||
use crate::subpath;
|
||||
use crate::table::Table;
|
||||
use crate::vector::Vector;
|
||||
use crate::vector::misc::HandleId;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
|
||||
trait CornerRadius {
|
||||
@@ -14,7 +14,7 @@ trait CornerRadius {
|
||||
impl CornerRadius for f64 {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
|
||||
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
}
|
||||
}
|
||||
impl CornerRadius for [f64; 4] {
|
||||
@@ -34,7 +34,7 @@ impl CornerRadius for [f64; 4] {
|
||||
} else {
|
||||
self
|
||||
};
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fn circle(
|
||||
radius: f64,
|
||||
) -> Table<Vector> {
|
||||
let radius = radius.abs();
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
@@ -63,14 +63,14 @@ fn arc(
|
||||
sweep_angle: Angle,
|
||||
arc_type: ArcType,
|
||||
) -> Table<Vector> {
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_arc(
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
|
||||
radius,
|
||||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
match arc_type {
|
||||
ArcType::Open => bezier_rs::ArcType::Open,
|
||||
ArcType::Closed => bezier_rs::ArcType::Closed,
|
||||
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
||||
ArcType::Open => subpath::ArcType::Open,
|
||||
ArcType::Closed => subpath::ArcType::Closed,
|
||||
ArcType::PieSlice => subpath::ArcType::PieSlice,
|
||||
},
|
||||
)))
|
||||
}
|
||||
@@ -90,7 +90,7 @@ fn ellipse(
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
|
||||
let mut ellipse = Vector::from_subpath(Subpath::new_ellipse(corner1, corner2));
|
||||
let mut ellipse = Vector::from_subpath(subpath::Subpath::new_ellipse(corner1, corner2));
|
||||
|
||||
let len = ellipse.segment_domain.ids().len();
|
||||
for i in 0..len {
|
||||
@@ -133,7 +133,7 @@ fn regular_polygon<T: AsU64>(
|
||||
) -> Table<Vector> {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
@@ -155,12 +155,12 @@ fn star<T: AsU64>(
|
||||
let diameter: f64 = radius_1 * 2.;
|
||||
let inner_diameter = radius_2 * 2.;
|
||||
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_line(start, end)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
trait GridSpacing {
|
||||
@@ -212,7 +212,7 @@ fn grid<T: GridSpacing>(
|
||||
if let Some(other_index) = to_index {
|
||||
vector
|
||||
.segment_domain
|
||||
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -249,7 +249,7 @@ fn grid<T: GridSpacing>(
|
||||
if let Some(other_index) = to_index {
|
||||
vector
|
||||
.segment_domain
|
||||
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -289,7 +289,7 @@ mod tests {
|
||||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
"Length of {} should be 10",
|
||||
@@ -304,7 +304,7 @@ mod tests {
|
||||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {}", angle)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::PointId;
|
||||
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use crate::vector::{SegmentId, Vector};
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
|
||||
@@ -115,18 +115,18 @@ pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
|
||||
|
||||
pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
BezierHandles::Linear => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(end);
|
||||
PathSeg::Line(Line::new(p0, p1))
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle);
|
||||
let p2 = dvec2_to_point(end);
|
||||
PathSeg::Quad(QuadBez::new(p0, p1, p2))
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle_start);
|
||||
let p2 = dvec2_to_point(handle_end);
|
||||
@@ -211,8 +211,8 @@ pub fn is_linear(segment: PathSeg) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an iterator over the coordinates of all points in a path segment.
|
||||
pub fn get_segment_points(segment: PathSeg) -> Vec<Point> {
|
||||
/// Get an vec of all the points in a path segment.
|
||||
pub fn pathseg_points_vec(segment: PathSeg) -> Vec<Point> {
|
||||
match segment {
|
||||
PathSeg::Line(line) => [line.p0, line.p1].to_vec(),
|
||||
PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),
|
||||
@@ -225,8 +225,8 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
|
||||
let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };
|
||||
let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };
|
||||
|
||||
let seg1_points = get_segment_points(seg1);
|
||||
let seg2_points = get_segment_points(seg2);
|
||||
let seg1_points = pathseg_points_vec(seg1);
|
||||
let seg2_points = pathseg_points_vec(seg2);
|
||||
|
||||
let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ mod vector_modification;
|
||||
mod vector_nodes;
|
||||
mod vector_types;
|
||||
|
||||
pub use bezier_rs;
|
||||
pub use reference_point::*;
|
||||
pub use style::PathStyle;
|
||||
pub use vector_nodes::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath};
|
||||
use crate::vector::misc::{HandleId, dvec2_to_point};
|
||||
use crate::vector::vector_types::Vector;
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{CubicBez, Line, PathSeg, QuadBez};
|
||||
@@ -442,11 +442,7 @@ impl SegmentDomain {
|
||||
zip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))
|
||||
}
|
||||
|
||||
pub(crate) fn pair_handles_and_points_mut_by_index(
|
||||
&mut self,
|
||||
index1: usize,
|
||||
index2: usize,
|
||||
) -> (&mut bezier_rs::BezierHandles, &mut usize, &mut usize, &mut bezier_rs::BezierHandles, &mut usize, &mut usize) {
|
||||
pub(crate) fn pair_handles_and_points_mut_by_index(&mut self, index1: usize, index2: usize) -> (&mut BezierHandles, &mut usize, &mut usize, &mut BezierHandles, &mut usize, &mut usize) {
|
||||
// Use split_at_mut to avoid multiple mutable borrows of the same slice
|
||||
let (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));
|
||||
let (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));
|
||||
@@ -686,25 +682,25 @@ impl Vector {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
|
||||
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> bezier_rs::Bezier {
|
||||
/// Construct a [`Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
|
||||
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> Bezier {
|
||||
let start = self.point_domain.positions()[start];
|
||||
let end = self.point_domain.positions()[end];
|
||||
bezier_rs::Bezier { start, end, handles }
|
||||
Bezier { start, end, handles }
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
|
||||
/// Tries to convert a segment with the specified id to a [`Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_from_id(&self, id: SegmentId) -> Option<Bezier> {
|
||||
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
|
||||
/// Tries to convert a segment with the specified id to the start and end points and a [`Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, Bezier)> {
|
||||
Some(self.segment_points_from_index(self.segment_domain.id_to_index(id)?))
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified index to the start and end points and a [`bezier_rs::Bezier`].
|
||||
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, bezier_rs::Bezier) {
|
||||
/// Tries to convert a segment with the specified index to the start and end points and a [`Bezier`].
|
||||
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, Bezier) {
|
||||
let start = self.segment_domain.start_point[index];
|
||||
let end = self.segment_domain.end_point[index];
|
||||
let start_id = self.point_domain.ids()[start];
|
||||
@@ -712,7 +708,7 @@ impl Vector {
|
||||
(start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index]))
|
||||
}
|
||||
|
||||
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_iter(&self) -> impl Iterator<Item = (SegmentId, PathSeg, PointId, PointId)> {
|
||||
let to_segment = |(((&handles, &id), &start), &end)| (id, self.path_segment_from_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
|
||||
|
||||
@@ -725,8 +721,8 @@ impl Vector {
|
||||
.map(to_segment)
|
||||
}
|
||||
|
||||
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, bezier_rs::Bezier, PointId, PointId)> + '_ {
|
||||
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, Bezier, PointId, PointId)> + '_ {
|
||||
let to_bezier = |(((&handles, &id), &start), &end)| (id, self.segment_to_bezier_with_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
|
||||
self.segment_domain
|
||||
.handles
|
||||
@@ -808,8 +804,8 @@ impl Vector {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
|
||||
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<bezier_rs::Subpath<PointId>> {
|
||||
/// Construct a [`Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
|
||||
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<Subpath<PointId>> {
|
||||
let mut first_point = None;
|
||||
let mut manipulators_list = Vec::new();
|
||||
let mut last: Option<(usize, BezierHandles)> = None;
|
||||
@@ -842,10 +838,10 @@ impl Vector {
|
||||
}
|
||||
}
|
||||
|
||||
Some(bezier_rs::Subpath::new(manipulators_list, closed))
|
||||
Some(Subpath::new(manipulators_list, closed))
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions.
|
||||
/// Construct a [`Bezier`] curve for each region, skipping invalid regions.
|
||||
pub fn region_manipulator_groups(&self) -> impl Iterator<Item = (RegionId, Vec<ManipulatorGroup<PointId>>)> + '_ {
|
||||
self.region_domain
|
||||
.id
|
||||
@@ -903,12 +899,12 @@ impl Vector {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve for stroke.
|
||||
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = bezier_rs::Subpath<PointId>> {
|
||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| bezier_rs::Subpath::new(manipulators_list, closed))
|
||||
/// Construct a [`Bezier`] curve for stroke.
|
||||
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = Subpath<PointId>> {
|
||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| Subpath::new(manipulators_list, closed))
|
||||
}
|
||||
|
||||
/// Construct and return an iterator of Vec of `(bezier_rs::ManipulatorGroup<PointId>], bool)` for stroke.
|
||||
/// Construct and return an iterator of Vec of `(ManipulatorGroup<PointId>], bool)` for stroke.
|
||||
/// The boolean in the tuple indicates if the path is closed.
|
||||
pub fn stroke_manipulator_groups(&self) -> impl Iterator<Item = (Vec<ManipulatorGroup<PointId>>, bool)> {
|
||||
self.build_stroke_path_iter()
|
||||
@@ -1094,7 +1090,7 @@ impl Iterator for StrokePathIter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl bezier_rs::Identifier for PointId {
|
||||
impl Identifier for PointId {
|
||||
fn new() -> Self {
|
||||
Self::generate()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::*;
|
||||
use crate::Ctx;
|
||||
use crate::subpath::BezierHandles;
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::uuid::{NodeId, generate_uuid};
|
||||
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, PathEl, Point};
|
||||
@@ -684,14 +684,15 @@ impl HandleExt for HandleId {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kurbo::{PathSeg, QuadBez};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::subpath::{Bezier, Subpath};
|
||||
|
||||
#[test]
|
||||
fn modify_new() {
|
||||
let vector = Vector::from_subpaths(
|
||||
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
|
||||
false,
|
||||
);
|
||||
let vector = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)], false);
|
||||
|
||||
let modify = VectorModification::create_from_vector(&vector);
|
||||
|
||||
@@ -702,14 +703,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn modify_existing() {
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
let subpaths = [
|
||||
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
|
||||
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
|
||||
Subpath::from_beziers(
|
||||
&[
|
||||
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
|
||||
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
|
||||
PathSeg::Quad(QuadBez::new(Point::new(0., 0.), Point::new(5., 10.), Point::new(10., 0.))),
|
||||
PathSeg::Quad(QuadBez::new(Point::new(10., 0.), Point::new(15., 10.), Point::new(20., 0.))),
|
||||
],
|
||||
false,
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::{PointId, SegmentDomain, SegmentId, StrokeId, Vector, VectorExt};
|
||||
use crate::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use crate::table::{Table, TableRow, TableRowMut};
|
||||
use crate::transform::{Footprint, ReferencePoint, Transform};
|
||||
use crate::vector::PointDomain;
|
||||
@@ -17,7 +18,6 @@ use crate::vector::misc::{handles_to_segment, segment_to_handles};
|
||||
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use crate::vector::{FillId, RegionId};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use core::f64::consts::PI;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -827,11 +827,11 @@ async fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(tr
|
||||
|
||||
if points_count > 2 {
|
||||
(0..points_count - 1).for_each(|i| {
|
||||
segment_domain.push(SegmentId::generate(), i, i + 1, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||
segment_domain.push(SegmentId::generate(), i, i + 1, BezierHandles::Linear, StrokeId::generate());
|
||||
});
|
||||
|
||||
if closed {
|
||||
segment_domain.push(SegmentId::generate(), points_count - 1, 0, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||
segment_domain.push(SegmentId::generate(), points_count - 1, 0, BezierHandles::Linear, StrokeId::generate());
|
||||
|
||||
row.element
|
||||
.region_domain
|
||||
@@ -1351,7 +1351,7 @@ async fn spline(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
|
||||
|
||||
let handle_start = first_handles[i];
|
||||
let handle_end = positions[next_index] * 2. - first_handles[next_index];
|
||||
let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end };
|
||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||
|
||||
segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id);
|
||||
}
|
||||
@@ -1405,14 +1405,14 @@ async fn jitter_points(
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
*handle_start += start_delta;
|
||||
*handle_end += end_delta;
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
*handle = row.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
}
|
||||
bezier_rs::BezierHandles::Linear => {}
|
||||
BezierHandles::Linear => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1846,7 +1846,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||
let mut next_id = vector.segment_domain.next_id();
|
||||
|
||||
for &[start, end] in new_segments {
|
||||
let handles = bezier_rs::BezierHandles::Linear;
|
||||
let handles = BezierHandles::Linear;
|
||||
vector.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ pub use super::vector_attributes::*;
|
||||
pub use super::vector_modification::*;
|
||||
use crate::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use crate::math::quad::Quad;
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup, Subpath};
|
||||
use crate::table::Table;
|
||||
use crate::transform::Transform;
|
||||
use crate::vector::click_target::{ClickTargetType, FreePoint};
|
||||
use crate::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use crate::{AlphaBlending, Color, Graphic};
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use core::borrow::Borrow;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -60,15 +60,15 @@ impl std::hash::Hash for Vector {
|
||||
|
||||
impl Vector {
|
||||
/// Add a Bezier-rs subpath to this path.
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
|
||||
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
||||
let subpath: &Subpath<PointId> = subpath.borrow();
|
||||
let stroke_id = StrokeId::ZERO;
|
||||
let mut point_id = self.point_domain.next_id();
|
||||
|
||||
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
|
||||
(None, None) => bezier_rs::BezierHandles::Linear,
|
||||
(Some(handle), None) | (None, Some(handle)) => bezier_rs::BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||
(None, None) => BezierHandles::Linear,
|
||||
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
let [mut first_seg, mut last_seg] = [None, None];
|
||||
let mut segment_id = self.segment_domain.next_id();
|
||||
@@ -132,7 +132,7 @@ impl Vector {
|
||||
}
|
||||
|
||||
/// Construct some new vector path from a single Bezier-rs subpath with an identity transform and black fill.
|
||||
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
|
||||
pub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self {
|
||||
Self::from_subpaths([subpath], false)
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ impl Vector {
|
||||
}
|
||||
|
||||
/// Construct some new vector path from Bezier-rs subpaths with an identity transform and black fill.
|
||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||
let mut vector = Self::default();
|
||||
|
||||
for subpath in subpaths.into_iter() {
|
||||
@@ -185,7 +185,7 @@ impl Vector {
|
||||
|
||||
for (start, end) in segments_to_add {
|
||||
let segment_id = self.segment_domain.next_id().next_id();
|
||||
self.segment_domain.push(segment_id, start, end, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
self.segment_domain.push(segment_id, start, end, BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,14 +244,19 @@ impl Vector {
|
||||
self.segment_domain.end_point().iter().map(|&index| self.point_domain.ids()[index])
|
||||
}
|
||||
|
||||
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
|
||||
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: (Option<DVec2>, Option<DVec2>), stroke: StrokeId) {
|
||||
let [Some(start), Some(end)] = [start, end].map(|id| self.point_domain.resolve_id(id)) else {
|
||||
return;
|
||||
};
|
||||
let handles = match handles {
|
||||
(None, None) => BezierHandles::Linear,
|
||||
(None, Some(handle)) | (Some(handle), None) => BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
self.segment_domain.push(id, start, end, handles, stroke)
|
||||
}
|
||||
|
||||
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
|
||||
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut BezierHandles, PointId, PointId)> {
|
||||
self.segment_domain
|
||||
.handles_mut()
|
||||
.map(|(id, handles, start, end)| (id, handles, self.point_domain.ids()[start], self.point_domain.ids()[end]))
|
||||
@@ -515,9 +520,11 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kurbo::{CubicBez, PathSeg, Point};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
|
||||
fn assert_subpath_eq(generated: &[Subpath<PointId>], expected: &[Subpath<PointId>]) {
|
||||
assert_eq!(generated.len(), expected.len());
|
||||
for (generated, expected) in generated.iter().zip(expected) {
|
||||
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
|
||||
@@ -532,10 +539,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn construct_closed_subpath() {
|
||||
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let vector = Vector::from_subpath(&circle);
|
||||
assert_eq!(vector.point_domain.ids().len(), 4);
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 4);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
@@ -545,11 +552,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn construct_open_subpath() {
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
||||
let subpath = Subpath::from_bezier(bezier);
|
||||
let vector = Vector::from_subpath(&subpath);
|
||||
assert_eq!(vector.point_domain.ids().len(), 2);
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths, vec![bezier]);
|
||||
|
||||
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
@@ -558,14 +565,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn construct_many_subpath() {
|
||||
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let curve = bezier_rs::Subpath::from_bezier(&curve);
|
||||
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
||||
let curve = Subpath::from_bezier(curve);
|
||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
|
||||
let vector = Vector::from_subpaths([&curve, &circle], false);
|
||||
assert_eq!(vector.point_domain.ids().len(), 6);
|
||||
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 5);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user