Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 121 additions & 47 deletions node-graph/gcore/src/vector/vector_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);

// Align point counts by inserting mid‐segment 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 });
let source_bezpaths = source_instance.instance.stroke_bezpath_iter();
let target_bezpaths = target_instance.instance.stroke_bezpath_iter();

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 {
Expand Down
Loading