mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +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:
4
node-graph/gcore/src/subpath/consts.rs
Normal file
4
node-graph/gcore/src/subpath/consts.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
// Implementation constants
|
||||
|
||||
/// Constant used to determine if `f64`s are equivalent.
|
||||
pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
||||
318
node-graph/gcore/src/subpath/core.rs
Normal file
318
node-graph/gcore/src/subpath/core.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
use super::consts::*;
|
||||
use super::*;
|
||||
use crate::vector::misc::point_to_dvec2;
|
||||
use glam::DVec2;
|
||||
use kurbo::PathSeg;
|
||||
|
||||
pub struct PathSegPoints {
|
||||
pub p0: DVec2,
|
||||
pub p1: Option<DVec2>,
|
||||
pub p2: Option<DVec2>,
|
||||
pub p3: DVec2,
|
||||
}
|
||||
|
||||
impl PathSegPoints {
|
||||
pub fn new(p0: DVec2, p1: Option<DVec2>, p2: Option<DVec2>, p3: DVec2) -> Self {
|
||||
Self { p0, p1, p2, p3 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pathseg_points(segment: PathSeg) -> PathSegPoints {
|
||||
match segment {
|
||||
PathSeg::Line(line) => PathSegPoints::new(point_to_dvec2(line.p0), None, None, point_to_dvec2(line.p1)),
|
||||
PathSeg::Quad(quad) => PathSegPoints::new(point_to_dvec2(quad.p0), None, Some(point_to_dvec2(quad.p1)), point_to_dvec2(quad.p2)),
|
||||
PathSeg::Cubic(cube) => PathSegPoints::new(point_to_dvec2(cube.p0), Some(point_to_dvec2(cube.p1)), Some(point_to_dvec2(cube.p2)), point_to_dvec2(cube.p3)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
|
||||
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
|
||||
#[track_caller]
|
||||
pub fn new(manipulator_groups: Vec<ManipulatorGroup<PointId>>, closed: bool) -> Self {
|
||||
assert!(!closed || !manipulator_groups.is_empty(), "A closed Subpath must contain more than 0 ManipulatorGroups.");
|
||||
Self { manipulator_groups, closed }
|
||||
}
|
||||
|
||||
/// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`.
|
||||
pub fn from_bezier(segment: PathSeg) -> Self {
|
||||
let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(segment);
|
||||
Subpath::new(vec![ManipulatorGroup::new(p0, None, p1), ManipulatorGroup::new(p3, p2, None)], false)
|
||||
}
|
||||
|
||||
/// Creates a subpath from a slice of [Bezier]. When two consecutive Beziers do not share an end and start point, this function
|
||||
/// resolves the discrepancy by simply taking the start-point of the second Bezier as the anchor of the Manipulator Group.
|
||||
pub fn from_beziers(beziers: &[PathSeg], closed: bool) -> Self {
|
||||
assert!(!closed || beziers.len() > 1, "A closed Subpath must contain at least 1 Bezier.");
|
||||
if beziers.is_empty() {
|
||||
return Subpath::new(vec![], closed);
|
||||
}
|
||||
|
||||
let beziers: Vec<_> = beziers.iter().map(|b| pathseg_points(*b)).collect();
|
||||
|
||||
let first = beziers.first().unwrap();
|
||||
let mut manipulator_groups = vec![ManipulatorGroup {
|
||||
anchor: first.p0,
|
||||
in_handle: None,
|
||||
out_handle: first.p1,
|
||||
id: PointId::new(),
|
||||
}];
|
||||
let mut inner_groups: Vec<ManipulatorGroup<PointId>> = beziers
|
||||
.windows(2)
|
||||
.map(|bezier_pair| ManipulatorGroup {
|
||||
anchor: bezier_pair[1].p0,
|
||||
in_handle: bezier_pair[0].p2,
|
||||
out_handle: bezier_pair[1].p1,
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>();
|
||||
manipulator_groups.append(&mut inner_groups);
|
||||
|
||||
let last = beziers.last().unwrap();
|
||||
if !closed {
|
||||
manipulator_groups.push(ManipulatorGroup {
|
||||
anchor: last.p3,
|
||||
in_handle: last.p2,
|
||||
out_handle: None,
|
||||
id: PointId::new(),
|
||||
});
|
||||
return Subpath::new(manipulator_groups, false);
|
||||
}
|
||||
|
||||
manipulator_groups[0].in_handle = last.p2;
|
||||
Subpath::new(manipulator_groups, true)
|
||||
}
|
||||
|
||||
/// Returns true if the `Subpath` contains no [ManipulatorGroup].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.manipulator_groups.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the number of [ManipulatorGroup]s contained within the `Subpath`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.manipulator_groups.len()
|
||||
}
|
||||
|
||||
/// Returns the number of segments contained within the `Subpath`.
|
||||
pub fn len_segments(&self) -> usize {
|
||||
let mut number_of_curves = self.len();
|
||||
if !self.closed && number_of_curves > 0 {
|
||||
number_of_curves -= 1
|
||||
}
|
||||
number_of_curves
|
||||
}
|
||||
|
||||
/// Returns a copy of the bezier segment at the given segment index, if this segment exists.
|
||||
pub fn get_segment(&self, segment_index: usize) -> Option<PathSeg> {
|
||||
if segment_index >= self.len_segments() {
|
||||
return None;
|
||||
}
|
||||
Some(self[segment_index].to_bezier(&self[(segment_index + 1) % self.len()]))
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
||||
pub fn iter(&self) -> SubpathIter<'_, PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
is_always_closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.
|
||||
pub fn iter_closed(&self) -> SubpathIter<'_, PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
is_always_closed: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<PointId>] {
|
||||
&self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<PointId>> {
|
||||
&mut self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a vector of all the anchors (DVec2) for this `Subpath`.
|
||||
pub fn anchors(&self) -> Vec<DVec2> {
|
||||
self.manipulator_groups().iter().map(|group| group.anchor).collect()
|
||||
}
|
||||
|
||||
/// Returns if the Subpath is equivalent to a single point.
|
||||
pub fn is_point(&self) -> bool {
|
||||
if self.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let point = self.manipulator_groups[0].anchor;
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.all(|manipulator_group| manipulator_group.anchor.abs_diff_eq(point, MAX_ABSOLUTE_DIFFERENCE))
|
||||
}
|
||||
|
||||
/// Construct a [Subpath] from an iter of anchor positions.
|
||||
pub fn from_anchors(anchor_positions: impl IntoIterator<Item = DVec2>, closed: bool) -> Self {
|
||||
Self::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor(anchor)).collect(), closed)
|
||||
}
|
||||
|
||||
pub fn from_anchors_linear(anchor_positions: impl IntoIterator<Item = DVec2>, closed: bool) -> Self {
|
||||
Self::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor_linear(anchor)).collect(), closed)
|
||||
}
|
||||
|
||||
/// Constructs a rectangle with `corner1` and `corner2` as the two corners.
|
||||
pub fn new_rect(corner1: DVec2, corner2: DVec2) -> Self {
|
||||
Self::from_anchors_linear([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true)
|
||||
}
|
||||
|
||||
/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.
|
||||
pub fn new_rounded_rect(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {
|
||||
if corner_radii.iter().all(|radii| radii.abs() < f64::EPSILON * 100.) {
|
||||
return Self::new_rect(corner1, corner2);
|
||||
}
|
||||
|
||||
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
||||
|
||||
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<PointId>> {
|
||||
let point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
let point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
if radius == 0. {
|
||||
return vec![ManipulatorGroup::new_anchor(point1), ManipulatorGroup::new_anchor(point2)];
|
||||
}
|
||||
|
||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||
let handle_offset = radius * HANDLE_OFFSET_FACTOR;
|
||||
vec![
|
||||
ManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),
|
||||
ManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),
|
||||
]
|
||||
};
|
||||
Self::new(
|
||||
[
|
||||
new_arc(DVec2::new(corner1.x + corner_radii[0], corner1.y + corner_radii[0]), DVec2::new(corner1.x, corner1.y), corner_radii[0]),
|
||||
new_arc(DVec2::new(corner2.x - corner_radii[1], corner1.y + corner_radii[1]), DVec2::new(corner2.x, corner1.y), corner_radii[1]),
|
||||
new_arc(DVec2::new(corner2.x - corner_radii[2], corner2.y - corner_radii[2]), DVec2::new(corner2.x, corner2.y), corner_radii[2]),
|
||||
new_arc(DVec2::new(corner1.x + corner_radii[3], corner2.y - corner_radii[3]), DVec2::new(corner1.x, corner2.y), corner_radii[3]),
|
||||
]
|
||||
.concat(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box.
|
||||
pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self {
|
||||
let size = (corner1 - corner2).abs();
|
||||
let center = (corner1 + corner2) / 2.;
|
||||
let top = DVec2::new(center.x, corner1.y);
|
||||
let bottom = DVec2::new(center.x, corner2.y);
|
||||
let left = DVec2::new(corner1.x, center.y);
|
||||
let right = DVec2::new(corner2.x, center.y);
|
||||
|
||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||
let handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5;
|
||||
|
||||
let manipulator_groups = vec![
|
||||
ManipulatorGroup::new(top, Some(top - handle_offset * DVec2::X), Some(top + handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(right, Some(right - handle_offset * DVec2::Y), Some(right + handle_offset * DVec2::Y)),
|
||||
ManipulatorGroup::new(bottom, Some(bottom + handle_offset * DVec2::X), Some(bottom - handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(left, Some(left + handle_offset * DVec2::Y), Some(left - handle_offset * DVec2::Y)),
|
||||
];
|
||||
Self::new(manipulator_groups, true)
|
||||
}
|
||||
|
||||
/// Constructs an arc by a `radius`, `angle_start` and `angle_size`. Angles must be in radians. Slice option makes it look like pie or pacman.
|
||||
pub fn new_arc(radius: f64, start_angle: f64, sweep_angle: f64, arc_type: ArcType) -> Self {
|
||||
// Prevents glitches from numerical imprecision that have been observed during animation playback after about a minute
|
||||
let start_angle = start_angle % (std::f64::consts::TAU * 2.);
|
||||
let sweep_angle = sweep_angle % (std::f64::consts::TAU * 2.);
|
||||
|
||||
let original_start_angle = start_angle;
|
||||
let sweep_angle_sign = sweep_angle.signum();
|
||||
|
||||
let mut start_angle = 0.;
|
||||
let mut sweep_angle = sweep_angle.abs();
|
||||
|
||||
if (sweep_angle / std::f64::consts::TAU).floor() as u32 % 2 == 0 {
|
||||
sweep_angle %= std::f64::consts::TAU;
|
||||
} else {
|
||||
start_angle = sweep_angle % std::f64::consts::TAU;
|
||||
sweep_angle = std::f64::consts::TAU - start_angle;
|
||||
}
|
||||
|
||||
sweep_angle *= sweep_angle_sign;
|
||||
start_angle *= sweep_angle_sign;
|
||||
start_angle += original_start_angle;
|
||||
|
||||
let closed = arc_type == ArcType::Closed;
|
||||
let slice = arc_type == ArcType::PieSlice;
|
||||
|
||||
let center = DVec2::new(0., 0.);
|
||||
let segments = (sweep_angle.abs() / (std::f64::consts::PI / 4.)).ceil().max(1.) as usize;
|
||||
let step = sweep_angle / segments as f64;
|
||||
let factor = 4. / 3. * (step / 2.).sin() / (1. + (step / 2.).cos());
|
||||
|
||||
let mut manipulator_groups = Vec::with_capacity(segments);
|
||||
let mut prev_in_handle = None;
|
||||
let mut prev_end = DVec2::new(0., 0.);
|
||||
|
||||
for i in 0..segments {
|
||||
let start_angle = start_angle + step * i as f64;
|
||||
let end_angle = start_angle + step;
|
||||
let start_vec = DVec2::from_angle(start_angle);
|
||||
let end_vec = DVec2::from_angle(end_angle);
|
||||
|
||||
let start = center + radius * start_vec;
|
||||
let end = center + radius * end_vec;
|
||||
|
||||
let handle_start = start + start_vec.perp() * radius * factor;
|
||||
let handle_end = end - end_vec.perp() * radius * factor;
|
||||
|
||||
manipulator_groups.push(ManipulatorGroup::new(start, prev_in_handle, Some(handle_start)));
|
||||
prev_in_handle = Some(handle_end);
|
||||
prev_end = end;
|
||||
}
|
||||
manipulator_groups.push(ManipulatorGroup::new(prev_end, prev_in_handle, None));
|
||||
|
||||
if slice {
|
||||
manipulator_groups.push(ManipulatorGroup::new(center, None, None));
|
||||
}
|
||||
|
||||
Self::new(manipulator_groups, closed || slice)
|
||||
}
|
||||
|
||||
/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.
|
||||
pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {
|
||||
let sides = sides.max(3);
|
||||
let angle_increment = std::f64::consts::TAU / (sides as f64);
|
||||
let anchor_positions = (0..sides).map(|i| {
|
||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||
let center = center + DVec2::ONE * radius;
|
||||
DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5
|
||||
});
|
||||
Self::from_anchors(anchor_positions, true)
|
||||
}
|
||||
|
||||
/// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`.
|
||||
pub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self {
|
||||
let sides = sides.max(2);
|
||||
let angle_increment = 0.5 * std::f64::consts::TAU / (sides as f64);
|
||||
let anchor_positions = (0..sides * 2).map(|i| {
|
||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||
let center = center + DVec2::ONE * radius;
|
||||
let r = if i % 2 == 0 { radius } else { inner_radius };
|
||||
DVec2::new(center.x + r * f64::cos(angle), center.y + r * f64::sin(angle)) * 0.5
|
||||
});
|
||||
Self::from_anchors(anchor_positions, true)
|
||||
}
|
||||
|
||||
/// Constructs a line from `p1` to `p2`
|
||||
pub fn new_line(p1: DVec2, p2: DVec2) -> Self {
|
||||
Self::from_anchors([p1, p2], false)
|
||||
}
|
||||
}
|
||||
114
node-graph/gcore/src/subpath/lookup.rs
Normal file
114
node-graph/gcore/src/subpath/lookup.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use super::*;
|
||||
use crate::math::polynomial::pathseg_to_parametric_polynomial;
|
||||
use crate::vector::algorithms::bezpath_algorithms::pathseg_length_centroid_and_length;
|
||||
use crate::vector::algorithms::intersection::{filtered_all_segment_intersections, pathseg_self_intersections};
|
||||
use glam::DVec2;
|
||||
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Returns a list of `t` values that correspond to all the self intersection points of the subpath always considering it as a closed subpath. The index and `t` value of both will be returned that corresponds to a point.
|
||||
/// The points will be sorted based on their index and `t` repsectively.
|
||||
/// - `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 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
|
||||
///
|
||||
/// **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 all_self_intersections(&self, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||
let mut intersections_vec = Vec::new();
|
||||
let err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
||||
let num_curves = self.len();
|
||||
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
||||
self.iter_closed().enumerate().for_each(|(i, other)| {
|
||||
intersections_vec.extend(pathseg_self_intersections(other, accuracy, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
||||
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
||||
intersections_vec.extend(
|
||||
filtered_all_segment_intersections(curve, other, accuracy, minimum_separation)
|
||||
.iter()
|
||||
.filter(|&value| (j != i + 1 || value.0 > err || (1. - value.1) > err) && (j != num_curves - 1 || i != 0 || value.1 > err || (1. - value.0) > err))
|
||||
.flat_map(|value| [(j, value.0), (i, value.1)]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
intersections_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
intersections_vec
|
||||
}
|
||||
|
||||
/// Return the area centroid, together with the area, of the `Subpath` always considering it as a closed subpath. The area will always be a positive value.
|
||||
///
|
||||
/// The area centroid is the center of mass for the area of a solid shape's interior.
|
||||
/// An infinitely flat material forming the subpath's closed shape would balance at this point.
|
||||
///
|
||||
/// It will return `None` if no manipulator is present. If the area is less than `error`, it will return `Some((DVec2::NAN, 0.))`.
|
||||
///
|
||||
/// Because the calculation of area and centroid for self-intersecting path requires finding the intersections, the following parameters are used:
|
||||
/// - `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 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.
|
||||
///
|
||||
/// **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 area_centroid_and_area(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Option<(DVec2, f64)> {
|
||||
let all_intersections = self.all_self_intersections(error, minimum_separation);
|
||||
let mut current_sign: f64 = 1.;
|
||||
|
||||
let (x_sum, y_sum, area) = self
|
||||
.iter_closed()
|
||||
.enumerate()
|
||||
.map(|(index, bezier)| {
|
||||
let (f_x, f_y) = pathseg_to_parametric_polynomial(bezier);
|
||||
let (f_x, f_y) = (f_x.as_size::<10>().unwrap(), f_y.as_size::<10>().unwrap());
|
||||
let f_y_prime = f_y.derivative();
|
||||
let f_x_prime = f_x.derivative();
|
||||
let f_xy = &f_x * &f_y;
|
||||
|
||||
let mut x_part = &f_xy * &f_x_prime;
|
||||
let mut y_part = &f_xy * &f_y_prime;
|
||||
let mut area_part = &f_x * &f_y_prime;
|
||||
x_part.antiderivative_mut();
|
||||
y_part.antiderivative_mut();
|
||||
area_part.antiderivative_mut();
|
||||
|
||||
let mut curve_sum_x = -current_sign * x_part.eval(0.);
|
||||
let mut curve_sum_y = -current_sign * y_part.eval(0.);
|
||||
let mut curve_sum_area = -current_sign * area_part.eval(0.);
|
||||
for (_, t) in all_intersections.iter().filter(|(i, _)| *i == index) {
|
||||
curve_sum_x += 2. * current_sign * x_part.eval(*t);
|
||||
curve_sum_y += 2. * current_sign * y_part.eval(*t);
|
||||
curve_sum_area += 2. * current_sign * area_part.eval(*t);
|
||||
current_sign *= -1.;
|
||||
}
|
||||
curve_sum_x += current_sign * x_part.eval(1.);
|
||||
curve_sum_y += current_sign * y_part.eval(1.);
|
||||
curve_sum_area += current_sign * area_part.eval(1.);
|
||||
|
||||
(-curve_sum_x, curve_sum_y, curve_sum_area)
|
||||
})
|
||||
.reduce(|(x1, y1, area1), (x2, y2, area2)| (x1 + x2, y1 + y2, area1 + area2))?;
|
||||
|
||||
if area.abs() < error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE) {
|
||||
return Some((DVec2::NAN, 0.));
|
||||
}
|
||||
|
||||
Some((DVec2::new(x_sum / area, y_sum / area), area.abs()))
|
||||
}
|
||||
|
||||
/// Return the approximation of the length centroid, together with the length, of the `Subpath`.
|
||||
///
|
||||
/// The length centroid is the center of mass for the arc length of the solid shape's perimeter.
|
||||
/// An infinitely thin wire forming the subpath's closed shape would balance at this point.
|
||||
///
|
||||
/// It will return `None` if no manipulator is present.
|
||||
/// - `accuracy` is used to approximate the curve.
|
||||
/// - `always_closed` is to consider the subpath as closed always.
|
||||
pub fn length_centroid_and_length(&self, accuracy: Option<f64>, always_closed: bool) -> Option<(DVec2, f64)> {
|
||||
if always_closed { self.iter_closed() } else { self.iter() }
|
||||
.map(|bezier| pathseg_length_centroid_and_length(bezier, accuracy))
|
||||
.map(|(centroid, length)| (centroid * length, length))
|
||||
.reduce(|(centroid_part1, length1), (centroid_part2, length2)| (centroid_part1 + centroid_part2, length1 + length2))
|
||||
.map(|(centroid_part, length)| (centroid_part / length, length))
|
||||
.map(|(centroid_part, length)| (DVec2::new(centroid_part.x, centroid_part.y), length))
|
||||
}
|
||||
}
|
||||
52
node-graph/gcore/src/subpath/manipulators.rs
Normal file
52
node-graph/gcore/src/subpath/manipulators.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
// use super::utils::{SubpathTValue};
|
||||
use super::*;
|
||||
|
||||
impl<PointId: super::structs::Identifier> Subpath<PointId> {
|
||||
/// Get whether the subpath is closed.
|
||||
pub fn closed(&self) -> bool {
|
||||
self.closed
|
||||
}
|
||||
|
||||
/// Set whether the subpath is closed.
|
||||
pub fn set_closed(&mut self, new_closed: bool) {
|
||||
self.closed = new_closed;
|
||||
}
|
||||
|
||||
/// Access a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_from_id(&self, id: PointId) -> Option<&ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access a mutable [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_mut_from_id(&mut self, id: PointId) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access the index of a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_index_from_id(&self, id: PointId) -> Option<usize> {
|
||||
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Insert a manipulator group at an index.
|
||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Inserting non finite manipulator group");
|
||||
self.manipulator_groups.insert(index, group)
|
||||
}
|
||||
|
||||
/// Push a manipulator group to the end.
|
||||
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Pushing non finite manipulator group");
|
||||
self.manipulator_groups.push(group)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the last manipulator
|
||||
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.last_mut()
|
||||
}
|
||||
|
||||
/// Remove a manipulator group at an index.
|
||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<PointId> {
|
||||
self.manipulator_groups.remove(index)
|
||||
}
|
||||
}
|
||||
71
node-graph/gcore/src/subpath/mod.rs
Normal file
71
node-graph/gcore/src/subpath/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
mod consts;
|
||||
mod core;
|
||||
mod lookup;
|
||||
mod manipulators;
|
||||
mod solvers;
|
||||
mod structs;
|
||||
mod transform;
|
||||
|
||||
pub use core::*;
|
||||
use kurbo::PathSeg;
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
use std::ops::{Index, IndexMut};
|
||||
pub use structs::*;
|
||||
|
||||
/// Structure used to represent a path composed of [Bezier] curves.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Subpath<PointId: Identifier> {
|
||||
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||
pub closed: bool,
|
||||
}
|
||||
|
||||
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
|
||||
pub struct SubpathIter<'a, PointId: Identifier> {
|
||||
index: usize,
|
||||
subpath: &'a Subpath<PointId>,
|
||||
is_always_closed: bool,
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Index<usize> for Subpath<PointId> {
|
||||
type Output = ManipulatorGroup<PointId>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
|
||||
&self.manipulator_groups[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> IndexMut<usize> for Subpath<PointId> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
|
||||
&mut self.manipulator_groups[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Iterator for SubpathIter<'_, PointId> {
|
||||
type Item = PathSeg;
|
||||
|
||||
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.subpath.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let closed = if self.is_always_closed { true } else { self.subpath.closed };
|
||||
let len = self.subpath.len() - 1 + if closed { 1 } else { 0 };
|
||||
if self.index >= len {
|
||||
return None;
|
||||
}
|
||||
let start_index = self.index;
|
||||
let end_index = (self.index + 1) % self.subpath.len();
|
||||
self.index += 1;
|
||||
|
||||
Some(self.subpath[start_index].to_bezier(&self.subpath[end_index]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Debug for Subpath<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("Subpath").field("closed", &self.closed).field("manipulator_groups", &self.manipulator_groups).finish()
|
||||
}
|
||||
}
|
||||
83
node-graph/gcore/src/subpath/solvers.rs
Normal file
83
node-graph/gcore/src/subpath/solvers.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::subpath::{Identifier, Subpath};
|
||||
use crate::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||
use crate::vector::misc::dvec2_to_point;
|
||||
use glam::DVec2;
|
||||
use kurbo::{Affine, BezPath, Shape};
|
||||
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
pub fn contains_point(&self, point: DVec2) -> bool {
|
||||
self.to_bezpath().contains(dvec2_to_point(point))
|
||||
}
|
||||
|
||||
pub fn to_bezpath(&self) -> BezPath {
|
||||
let mut bezpath = kurbo::BezPath::new();
|
||||
let mut out_handle;
|
||||
|
||||
let Some(first) = self.manipulator_groups.first() else { return bezpath };
|
||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
||||
out_handle = first.out_handle;
|
||||
|
||||
for manipulator in self.manipulator_groups.iter().skip(1) {
|
||||
match (out_handle, manipulator.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
}
|
||||
out_handle = manipulator.out_handle;
|
||||
}
|
||||
|
||||
if self.closed {
|
||||
match (out_handle, first.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
}
|
||||
bezpath.close_path();
|
||||
}
|
||||
bezpath
|
||||
}
|
||||
|
||||
/// Returns `true` if this subpath is completely inside the `other` subpath.
|
||||
pub fn is_inside_subpath(&self, other: &Subpath<PointId>, accuracy: Option<f64>, minimum_separation: Option<f64>) -> bool {
|
||||
bezpath_is_inside_bezpath(&self.to_bezpath(), &other.to_bezpath(), accuracy, minimum_separation)
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the bounding box of the subpath. Return `None` if the subpath is empty.
|
||||
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.map(|bezier| bezier.bounding_box())
|
||||
.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the bounding box of the subpath, after a given affine transform.
|
||||
pub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.map(|bezier| (Affine::new(transform.to_cols_array()) * bezier).bounding_box())
|
||||
.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the loose bounding box of the subpath (bounding box of all handles and anchors).
|
||||
pub fn loose_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])
|
||||
.flatten()
|
||||
.map(|pos| [pos, pos])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the loose bounding box of the subpath, after a given affine transform.
|
||||
pub fn loose_bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])
|
||||
.flatten()
|
||||
.map(|pos| transform.transform_point2(pos))
|
||||
.map(|pos| [pos, pos])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
}
|
||||
427
node-graph/gcore/src/subpath/structs.rs
Normal file
427
node-graph/gcore/src/subpath/structs.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
use crate::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use crate::vector::misc::{dvec2_to_point, handles_to_segment};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{CubicBez, Line, PathSeg, QuadBez, Shape};
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// An id type used for each [ManipulatorGroup].
|
||||
pub trait Identifier: Sized + Clone + PartialEq + Hash + 'static {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
/// An empty id type for use in tests
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[cfg(test)]
|
||||
pub(crate) struct EmptyId;
|
||||
|
||||
#[cfg(test)]
|
||||
impl Identifier for EmptyId {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroup<PointId: Identifier> {
|
||||
pub anchor: DVec2,
|
||||
pub in_handle: Option<DVec2>,
|
||||
pub out_handle: Option<DVec2>,
|
||||
pub id: PointId,
|
||||
}
|
||||
|
||||
// TODO: Remove once we no longer need to hash floats in Graphite
|
||||
impl<PointId: Identifier> Hash for ManipulatorGroup<PointId> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.in_handle.is_some().hash(state);
|
||||
if let Some(in_handle) = self.in_handle {
|
||||
in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.out_handle.is_some().hash(state);
|
||||
if let Some(out_handle) = self.out_handle {
|
||||
out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Debug for ManipulatorGroup<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("ManipulatorGroup")
|
||||
.field("anchor", &self.anchor)
|
||||
.field("in_handle", &self.in_handle)
|
||||
.field("out_handle", &self.out_handle)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> ManipulatorGroup<PointId> {
|
||||
/// Construct a new manipulator group from an anchor, in handle and out handle
|
||||
pub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {
|
||||
let id = PointId::new();
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
/// Construct a new manipulator point with just an anchor position
|
||||
pub fn new_anchor(anchor: DVec2) -> Self {
|
||||
Self::new(anchor, Some(anchor), Some(anchor))
|
||||
}
|
||||
|
||||
pub fn new_anchor_linear(anchor: DVec2) -> Self {
|
||||
Self::new(anchor, None, None)
|
||||
}
|
||||
|
||||
/// Construct a new manipulator group from an anchor, in handle, out handle and an id
|
||||
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: PointId) -> Self {
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
/// Construct a new manipulator point with just an anchor position and an id
|
||||
pub fn new_anchor_with_id(anchor: DVec2, id: PointId) -> Self {
|
||||
Self::new_with_id(anchor, Some(anchor), Some(anchor), id)
|
||||
}
|
||||
|
||||
/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.
|
||||
pub fn to_bezier(&self, end_group: &ManipulatorGroup<PointId>) -> PathSeg {
|
||||
let start = self.anchor;
|
||||
let end = end_group.anchor;
|
||||
let out_handle = self.out_handle;
|
||||
let in_handle = end_group.in_handle;
|
||||
|
||||
match (out_handle, in_handle) {
|
||||
(Some(handle1), Some(handle2)) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle1), dvec2_to_point(handle2), dvec2_to_point(end))),
|
||||
(Some(handle), None) | (None, Some(handle)) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(handle), dvec2_to_point(end))),
|
||||
(None, None) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a transformation to all of the [ManipulatorGroup] points
|
||||
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
|
||||
self.anchor = affine_transform.transform_point2(self.anchor);
|
||||
self.in_handle = self.in_handle.map(|in_handle| affine_transform.transform_point2(in_handle));
|
||||
self.out_handle = self.out_handle.map(|out_handle| affine_transform.transform_point2(out_handle));
|
||||
}
|
||||
|
||||
/// Are all handles at finite positions
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.anchor.is_finite() && self.in_handle.is_none_or(|handle| handle.is_finite()) && self.out_handle.is_none_or(|handle| handle.is_finite())
|
||||
}
|
||||
|
||||
/// Reverse directions of handles
|
||||
pub fn flip(mut self) -> Self {
|
||||
std::mem::swap(&mut self.in_handle, &mut self.out_handle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_in_handle(&self) -> bool {
|
||||
self.in_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn has_out_handle(&self) -> bool {
|
||||
self.out_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn has_handle(anchor: DVec2, handle: DVec2) -> bool {
|
||||
!((handle.x - anchor.x).abs() < f64::EPSILON && (handle.y - anchor.y).abs() < f64::EPSILON)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum AppendType {
|
||||
IgnoreStart,
|
||||
SmoothJoin(f64),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ArcType {
|
||||
Open,
|
||||
Closed,
|
||||
PieSlice,
|
||||
}
|
||||
|
||||
/// Representation of the handle point(s) in a bezier segment.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BezierHandles {
|
||||
Linear,
|
||||
/// Handles for a quadratic curve.
|
||||
Quadratic {
|
||||
/// Point representing the location of the single handle.
|
||||
handle: DVec2,
|
||||
},
|
||||
/// Handles for a cubic curve.
|
||||
Cubic {
|
||||
/// Point representing the location of the handle associated to the start point.
|
||||
handle_start: DVec2,
|
||||
/// Point representing the location of the handle associated to the end point.
|
||||
handle_end: DVec2,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::hash::Hash for BezierHandles {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
BezierHandles::Linear => {}
|
||||
BezierHandles::Quadratic { handle } => handle.to_array().map(|v| v.to_bits()).hash(state),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end].map(|handle| handle.to_array().map(|v| v.to_bits())).hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BezierHandles {
|
||||
pub fn is_cubic(&self) -> bool {
|
||||
matches!(self, Self::Cubic { .. })
|
||||
}
|
||||
|
||||
pub fn is_finite(&self) -> bool {
|
||||
match self {
|
||||
BezierHandles::Linear => true,
|
||||
BezierHandles::Quadratic { handle } => handle.is_finite(),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => handle_start.is_finite() && handle_end.is_finite(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn start(&self) -> Option<DVec2> {
|
||||
match *self {
|
||||
BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } => Some(handle_start),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.
|
||||
pub fn end(&self) -> Option<DVec2> {
|
||||
match *self {
|
||||
BezierHandles::Cubic { handle_end, .. } => Some(handle_end),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_start(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } = self {
|
||||
*handle_start += delta
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_end(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_end, .. } = self {
|
||||
*handle_end += delta
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.
|
||||
#[must_use]
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {
|
||||
match *self {
|
||||
BezierHandles::Linear => Self::Linear,
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let handle = transformation_function(handle);
|
||||
Self::Quadratic { handle }
|
||||
}
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let handle_start = transformation_function(handle_start);
|
||||
let handle_end = transformation_function(handle_end);
|
||||
Self::Cubic { handle_start, handle_end }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn reversed(self) -> Self {
|
||||
match self {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => Self::Cubic {
|
||||
handle_start: handle_end,
|
||||
handle_end: handle_start,
|
||||
},
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a bezier curve with 2D points.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Bezier {
|
||||
/// Start point of the bezier curve.
|
||||
pub start: DVec2,
|
||||
/// End point of the bezier curve.
|
||||
pub end: DVec2,
|
||||
/// Handles of the bezier curve.
|
||||
pub handles: BezierHandles,
|
||||
}
|
||||
|
||||
impl Debug for Bezier {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let mut debug_struct = f.debug_struct("Bezier");
|
||||
let mut debug_struct_ref = debug_struct.field("start", &self.start);
|
||||
debug_struct_ref = match self.handles {
|
||||
BezierHandles::Linear => debug_struct_ref,
|
||||
BezierHandles::Quadratic { handle } => debug_struct_ref.field("handle", &handle),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => debug_struct_ref.field("handle_start", &handle_start).field("handle_end", &handle_end),
|
||||
};
|
||||
debug_struct_ref.field("end", &self.end).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality for the getters and setters of the various points in a Bezier
|
||||
impl Bezier {
|
||||
/// Set the coordinates of the start point.
|
||||
pub fn set_start(&mut self, s: DVec2) {
|
||||
self.start = s;
|
||||
}
|
||||
|
||||
/// Set the coordinates of the end point.
|
||||
pub fn set_end(&mut self, e: DVec2) {
|
||||
self.end = e;
|
||||
}
|
||||
|
||||
/// Set the coordinates of the first handle point. This represents the only handle in a quadratic segment. If used on a linear segment, it will be changed to a quadratic.
|
||||
pub fn set_handle_start(&mut self, h1: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => {
|
||||
self.handles = BezierHandles::Quadratic { handle: h1 };
|
||||
}
|
||||
BezierHandles::Quadratic { ref mut handle } => {
|
||||
*handle = h1;
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle_start, .. } => {
|
||||
*handle_start = h1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the coordinates of the second handle point. This will convert both linear and quadratic segments into cubic ones. For a linear segment, the first handle will be set to the start point.
|
||||
pub fn set_handle_end(&mut self, h2: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => {
|
||||
self.handles = BezierHandles::Cubic {
|
||||
handle_start: self.start,
|
||||
handle_end: h2,
|
||||
};
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
self.handles = BezierHandles::Cubic { handle_start: handle, handle_end: h2 };
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle_end, .. } => {
|
||||
*handle_end = h2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's start point.
|
||||
pub fn start(&self) -> DVec2 {
|
||||
self.start
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's end point.
|
||||
pub fn end(&self) -> DVec2 {
|
||||
self.end
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn handle_start(&self) -> Option<DVec2> {
|
||||
self.handles.start()
|
||||
}
|
||||
|
||||
/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.
|
||||
pub fn handle_end(&self) -> Option<DVec2> {
|
||||
self.handles.end()
|
||||
}
|
||||
|
||||
/// Get an iterator over the coordinates of all points in a vector.
|
||||
/// - For a linear segment, the order of the points will be: `start`, `end`.
|
||||
/// - For a quadratic segment, the order of the points will be: `start`, `handle`, `end`.
|
||||
/// - For a cubic segment, the order of the points will be: `start`, `handle_start`, `handle_end`, `end`.
|
||||
pub fn get_points(&self) -> impl Iterator<Item = DVec2> + use<> {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => [self.start, self.end, DVec2::ZERO, DVec2::ZERO].into_iter().take(2),
|
||||
BezierHandles::Quadratic { handle } => [self.start, handle, self.end, DVec2::ZERO].into_iter().take(3),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [self.start, handle_start, handle_end, self.end].into_iter().take(4),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a linear bezier using the provided coordinates as the start and end points.
|
||||
pub fn from_linear_coordinates(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Linear,
|
||||
end: DVec2::new(x2, y2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a linear bezier using the provided DVec2s as the start and end points.
|
||||
pub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Linear,
|
||||
end: p2,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a quadratic bezier using the provided coordinates as the start, handle, and end points.
|
||||
pub fn from_quadratic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Quadratic { handle: DVec2::new(x2, y2) },
|
||||
end: DVec2::new(x3, y3),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points.
|
||||
pub fn from_quadratic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Quadratic { handle: p2 },
|
||||
end: p3,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a cubic bezier using the provided coordinates as the start, handles, and end points.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Cubic {
|
||||
handle_start: DVec2::new(x2, y2),
|
||||
handle_end: DVec2::new(x3, y3),
|
||||
},
|
||||
end: DVec2::new(x4, y4),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a cubic bezier using the provided DVec2s as the start, handles, and end points.
|
||||
pub fn from_cubic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2, p4: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Cubic { handle_start: p2, handle_end: p3 },
|
||||
end: p4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier.
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier {
|
||||
Self {
|
||||
start: transformation_function(self.start),
|
||||
end: transformation_function(self.end),
|
||||
handles: self.handles.apply_transformation(transformation_function),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersections(&self, other: &Bezier, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<f64> {
|
||||
let this = handles_to_segment(self.start, self.handles, self.end);
|
||||
let other = handles_to_segment(other.start, other.handles, other.end);
|
||||
filtered_segment_intersections(this, other, accuracy, minimum_separation)
|
||||
}
|
||||
|
||||
pub fn winding(&self, point: DVec2) -> i32 {
|
||||
let this = handles_to_segment(self.start, self.handles, self.end);
|
||||
this.winding(dvec2_to_point(point))
|
||||
}
|
||||
}
|
||||
62
node-graph/gcore/src/subpath/transform.rs
Normal file
62
node-graph/gcore/src/subpath/transform.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use super::structs::Identifier;
|
||||
use super::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Returns [ManipulatorGroup]s with a reversed winding order.
|
||||
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>]) -> Vec<ManipulatorGroup<PointId>> {
|
||||
manipulator_groups
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|group| ManipulatorGroup {
|
||||
anchor: group.anchor,
|
||||
in_handle: group.out_handle,
|
||||
out_handle: group.in_handle,
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||
}
|
||||
|
||||
/// Returns a [Subpath] with a reversed winding order.
|
||||
/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction
|
||||
pub fn reverse(&self) -> Subpath<PointId> {
|
||||
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
|
||||
if self.closed {
|
||||
reversed.rotate_right(1);
|
||||
};
|
||||
Subpath {
|
||||
manipulator_groups: reversed,
|
||||
closed: self.closed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a transformation to all of the [ManipulatorGroup]s in the [Subpath].
|
||||
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
|
||||
for manipulator_group in &mut self.manipulator_groups {
|
||||
manipulator_group.apply_transform(affine_transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<PointId> {
|
||||
let mut rotated_subpath = self.clone();
|
||||
|
||||
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
|
||||
rotated_subpath.apply_transform(affine_transform);
|
||||
|
||||
rotated_subpath
|
||||
}
|
||||
|
||||
/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).
|
||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<PointId> {
|
||||
// Translate before and after the rotation to account for the pivot
|
||||
let translate: DAffine2 = DAffine2::from_translation(pivot);
|
||||
let rotate: DAffine2 = DAffine2::from_angle(angle);
|
||||
let translate_inverse = translate.inverse();
|
||||
|
||||
let mut rotated_subpath = self.clone();
|
||||
rotated_subpath.apply_transform(translate * rotate * translate_inverse);
|
||||
rotated_subpath
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user