Refactor the 'Morph' node to use Kurbo instead of Bezier-rs (#2696)

* fix morph node

* it worked!

* cleanup

* add comments

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Priyanshu 2025-06-09 03:43:10 +05:30 committed by Keavon Chambers
parent b0384adabc
commit 477a3f6670

View file

@ -12,11 +12,11 @@ use crate::vector::PointDomain;
use crate::vector::misc::dvec2_to_point;
use crate::vector::style::{LineCap, LineJoin};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue};
use bezier_rs::{Join, ManipulatorGroup, Subpath};
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Shape};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Point, Shape};
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
use std::f64::consts::TAU;
@ -1526,6 +1526,46 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction) -> VectorDataTable {
/// Subdivides the last segment of the bezpath to until it appends 'count' number of segments.
fn make_new_segments(bezpath: &mut BezPath, count: usize) {
let bezpath_segment_count = bezpath.segments().count();
if count == 0 || bezpath_segment_count == 0 {
return;
}
// Initially push the last segment of the bezpath
let mut new_segments = vec![bezpath.get_seg(bezpath_segment_count).unwrap()];
// Generate new segments by subdividing last segment
for _ in 0..count {
let last = new_segments.pop().unwrap();
let (first, second) = last.subdivide();
new_segments.push(first);
new_segments.push(second);
}
// Append the new segments.
if count != 0 {
// Remove the last segment as it is already appended to the new_segments.
let mut is_closed = false;
if let Some(last_element) = bezpath.pop() {
if last_element == PathEl::ClosePath {
is_closed = true;
_ = bezpath.pop();
}
}
for segment in new_segments {
bezpath.push(segment.as_path_el());
}
if is_closed {
bezpath.close_path();
}
}
}
let time = time.clamp(0., 1.);
let mut result_table = VectorDataTable::default();
@ -1542,65 +1582,99 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
let target_transform = target_instance.transform;
// Before and after paths
let source_paths = source_instance.instance.stroke_bezier_paths();
let target_paths = target_instance.instance.stroke_bezier_paths();
for (mut source_path, mut target_path) in source_paths.zip(target_paths) {
source_path.apply_transform(source_transform);
target_path.apply_transform(target_transform);
let source_bezpaths = source_instance.instance.stroke_bezpath_iter();
let target_bezpaths = target_instance.instance.stroke_bezpath_iter();
// Align point counts by inserting midsegment points until their counts match
while source_path.manipulator_groups().len() < target_path.manipulator_groups().len() {
let last = source_path.len() - 1;
source_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
}
while target_path.manipulator_groups().len() < source_path.manipulator_groups().len() {
let last = target_path.len() - 1;
target_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
for (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) {
if source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() {
continue;
}
source_bezpath.apply_affine(Affine::new(source_transform.to_cols_array()));
target_bezpath.apply_affine(Affine::new(target_transform.to_cols_array()));
let target_segment_len = target_bezpath.segments().count();
let source_segment_len = source_bezpath.segments().count();
// Insert new segments to align the number of segments in sorce_bezpath and target_bezpath.
make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len);
make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len);
let source_segments = source_bezpath.segments().collect::<Vec<PathSeg>>();
let target_segments = target_bezpath.segments().collect::<Vec<PathSeg>>();
// Interpolate anchors and handles
for (source_manipulators, target_manipulators) in source_path.manipulator_groups_mut().iter_mut().zip(target_path.manipulator_groups()) {
let source_anchor = source_manipulators.anchor;
let target_anchor = target_manipulators.anchor;
source_manipulators.anchor = source_anchor.lerp(target_anchor, time);
let source_in_handle = source_manipulators.in_handle.unwrap_or(source_anchor);
let target_in_handle = target_manipulators.in_handle.unwrap_or(target_anchor);
source_manipulators.in_handle = Some(source_in_handle.lerp(target_in_handle, time));
let source_out_handle = source_manipulators.out_handle.unwrap_or(source_anchor);
let target_out_handle = target_manipulators.out_handle.unwrap_or(target_anchor);
source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time));
for (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() {
match source_element {
PathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time),
PathEl::ClosePath => {}
elm => {
let mut source_segment = source_segments.get(i - 1).unwrap().to_cubic();
let target_segment = target_segments.get(i - 1).unwrap().to_cubic();
source_segment.p0 = source_segment.p0.lerp(target_segment.p0, time);
source_segment.p1 = source_segment.p1.lerp(target_segment.p1, time);
source_segment.p2 = source_segment.p2.lerp(target_segment.p2, time);
source_segment.p3 = source_segment.p3.lerp(target_segment.p3, time);
*elm = PathSeg::Cubic(source_segment).as_path_el();
}
}
}
vector_data_instance.append_subpath(source_path.clone(), true);
vector_data_instance.append_bezpath(source_bezpath.clone());
}
// Deal with unmatched extra paths by collapsing them
let source_paths_count = source_instance.instance.stroke_bezier_paths().count();
let target_paths_count = target_instance.instance.stroke_bezier_paths().count();
let source_paths = source_instance.instance.stroke_bezier_paths().skip(target_paths_count);
let target_paths = target_instance.instance.stroke_bezier_paths().skip(source_paths_count);
let source_paths_count = source_instance.instance.stroke_bezpath_iter().count();
let target_paths_count = target_instance.instance.stroke_bezpath_iter().count();
let source_paths = source_instance.instance.stroke_bezpath_iter().skip(target_paths_count);
let target_paths = target_instance.instance.stroke_bezpath_iter().skip(source_paths_count);
for mut source_path in source_paths {
source_path.apply_transform(source_transform);
let end = source_path.manipulator_groups().last().map(|group| group.anchor).unwrap_or_default();
for group in source_path.manipulator_groups_mut() {
group.anchor = group.anchor.lerp(end, time);
group.in_handle = group.in_handle.map(|handle| handle.lerp(end, time));
group.out_handle = group.out_handle.map(|handle| handle.lerp(end, time));
source_path.apply_affine(Affine::new(source_transform.to_cols_array()));
let end: Point = source_path.elements().last().and_then(|element| element.end_point()).unwrap_or_default();
for element in source_path.elements_mut() {
match element {
PathEl::MoveTo(point) => *point = point.lerp(end, time),
PathEl::LineTo(point) => *point = point.lerp(end, time),
PathEl::QuadTo(point, point1) => {
*point = point.lerp(end, time);
*point1 = point1.lerp(end, time);
}
PathEl::CurveTo(point, point1, point2) => {
*point = point.lerp(end, time);
*point1 = point1.lerp(end, time);
*point2 = point2.lerp(end, time);
}
PathEl::ClosePath => {}
}
}
vector_data_instance.append_subpath(source_path, true);
vector_data_instance.append_bezpath(source_path);
}
for mut target_path in target_paths {
target_path.apply_transform(target_transform);
let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
for group in target_path.manipulator_groups_mut() {
group.anchor = start.lerp(group.anchor, time);
group.in_handle = group.in_handle.map(|handle| start.lerp(handle, time));
group.out_handle = group.out_handle.map(|handle| start.lerp(handle, time));
target_path.apply_affine(Affine::new(source_transform.to_cols_array()));
let end: Point = target_path.elements().last().and_then(|element| element.end_point()).unwrap_or_default();
for element in target_path.elements_mut() {
match element {
PathEl::MoveTo(point) => *point = point.lerp(end, time),
PathEl::LineTo(point) => *point = point.lerp(end, time),
PathEl::QuadTo(point, point1) => {
*point = point.lerp(end, time);
*point1 = point1.lerp(end, time);
}
PathEl::CurveTo(point, point1, point2) => {
*point = point.lerp(end, time);
*point1 = point1.lerp(end, time);
*point2 = point2.lerp(end, time);
}
PathEl::ClosePath => {}
}
}
vector_data_instance.append_subpath(target_path, true);
vector_data_instance.append_bezpath(target_path);
}
result_table.push(Instance {