New node: 'Extrude' (#3414)
* Add extrude node * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
5ebf6d6bc0
commit
8383a3afac
|
|
@ -23,7 +23,7 @@ use graphene_std::raster::{
|
||||||
use graphene_std::table::{Table, TableRow};
|
use graphene_std::table::{Table, TableRow};
|
||||||
use graphene_std::text::{Font, TextAlign};
|
use graphene_std::text::{Font, TextAlign};
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
|
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
|
||||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||||
|
|
@ -219,6 +219,7 @@ pub(crate) fn property_from_type(
|
||||||
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
||||||
|
Some(x) if x == TypeId::of::<ExtrudeJoiningAlgorithm>() => enum_choice::<ExtrudeJoiningAlgorithm>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,7 @@ tagged_value! {
|
||||||
GridType(vector::misc::GridType),
|
GridType(vector::misc::GridType),
|
||||||
ArcType(vector::misc::ArcType),
|
ArcType(vector::misc::ArcType),
|
||||||
MergeByDistanceAlgorithm(vector::misc::MergeByDistanceAlgorithm),
|
MergeByDistanceAlgorithm(vector::misc::MergeByDistanceAlgorithm),
|
||||||
|
ExtrudeJoiningAlgorithm(vector::misc::ExtrudeJoiningAlgorithm),
|
||||||
PointSpacingType(vector::misc::PointSpacingType),
|
PointSpacingType(vector::misc::PointSpacingType),
|
||||||
SpiralType(vector::misc::SpiralType),
|
SpiralType(vector::misc::SpiralType),
|
||||||
#[serde(alias = "LineCap")]
|
#[serde(alias = "LineCap")]
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
||||||
|
|
@ -220,6 +221,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
||||||
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,16 @@ pub enum MergeByDistanceAlgorithm {
|
||||||
Topological,
|
Topological,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||||
|
#[widget(Radio)]
|
||||||
|
pub enum ExtrudeJoiningAlgorithm {
|
||||||
|
All,
|
||||||
|
#[default]
|
||||||
|
Extrema,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,10 @@ impl SegmentDomain {
|
||||||
self.end_point[segment_index] = new;
|
self.end_point[segment_index] = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_handles(&mut self, segment_index: usize, new: BezierHandles) {
|
||||||
|
self.handles[segment_index] = new;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handles(&self) -> &[BezierHandles] {
|
pub fn handles(&self) -> &[BezierHandles] {
|
||||||
&self.handles
|
&self.handles
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, evaluat
|
||||||
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||||
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
||||||
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
||||||
use vector_types::vector::misc::{CentroidType, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, point_to_dvec2};
|
use vector_types::vector::misc::{CentroidType, ExtrudeJoiningAlgorithm, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, point_to_dvec2};
|
||||||
use vector_types::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType, is_linear};
|
use vector_types::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType, is_linear};
|
||||||
use vector_types::vector::misc::{handles_to_segment, segment_to_handles};
|
use vector_types::vector::misc::{handles_to_segment, segment_to_handles};
|
||||||
use vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
use vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
||||||
|
|
@ -580,6 +580,210 @@ pub fn merge_by_distance(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod extrude_algorithms {
|
||||||
|
use glam::DVec2;
|
||||||
|
use kurbo::{ParamCurve, ParamCurveDeriv};
|
||||||
|
use vector_types::subpath::BezierHandles;
|
||||||
|
use vector_types::vector::StrokeId;
|
||||||
|
use vector_types::vector::misc::ExtrudeJoiningAlgorithm;
|
||||||
|
|
||||||
|
/// Convert [`vector_types::subpath::Bezier`] to [`kurbo::PathSeg`].
|
||||||
|
fn bezier_to_path_seg(bezier: vector_types::subpath::Bezier) -> kurbo::PathSeg {
|
||||||
|
let [start, end] = [(bezier.start().x, bezier.start().y), (bezier.end().x, bezier.end().y)];
|
||||||
|
match bezier.handles {
|
||||||
|
BezierHandles::Linear => kurbo::Line::new(start, end).into(),
|
||||||
|
BezierHandles::Quadratic { handle } => kurbo::QuadBez::new(start, (handle.x, handle.y), end).into(),
|
||||||
|
BezierHandles::Cubic { handle_start, handle_end } => kurbo::CubicBez::new(start, (handle_start.x, handle_start.y), (handle_end.x, handle_end.y), end).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert [`kurbo::CubicBez`] to [`vector_types::subpath::BezierHandles`].
|
||||||
|
fn cubic_to_handles(cubic_bez: kurbo::CubicBez) -> BezierHandles {
|
||||||
|
BezierHandles::Cubic {
|
||||||
|
handle_start: DVec2::new(cubic_bez.p1.x, cubic_bez.p1.y),
|
||||||
|
handle_end: DVec2::new(cubic_bez.p2.x, cubic_bez.p2.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the `t` values to split (where the tangent changes to be on the other side of the direction).
|
||||||
|
fn find_splits(cubic_segment: kurbo::CubicBez, direction: DVec2) -> impl Iterator<Item = f64> {
|
||||||
|
let derivative = cubic_segment.deriv();
|
||||||
|
let convert = |x: kurbo::Point| DVec2::new(x.x, x.y);
|
||||||
|
let derivative_points = [derivative.p0, derivative.p1, derivative.p2].map(convert);
|
||||||
|
|
||||||
|
let t_squared = derivative_points[0] - 2. * derivative_points[1] + derivative_points[2];
|
||||||
|
let t_scalar = -2. * derivative_points[0] + 2. * derivative_points[1];
|
||||||
|
let constant = derivative_points[0];
|
||||||
|
|
||||||
|
kurbo::common::solve_quadratic(constant.perp_dot(direction), t_scalar.perp_dot(direction), t_squared.perp_dot(direction))
|
||||||
|
.into_iter()
|
||||||
|
.filter(|&t| t > 1e-6 && t < 1. - 1e-6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split so segments no longer have tangents on both sides of the direction vector.
|
||||||
|
fn split(vector: &mut graphic_types::Vector, direction: DVec2) {
|
||||||
|
let segment_count = vector.segment_domain.ids().len();
|
||||||
|
let mut next_point = vector.point_domain.next_id();
|
||||||
|
let mut next_segment = vector.segment_domain.next_id();
|
||||||
|
|
||||||
|
for segment_index in 0..segment_count {
|
||||||
|
let (_, _, bezier) = vector.segment_points_from_index(segment_index);
|
||||||
|
let mut start_index = vector.segment_domain.start_point()[segment_index];
|
||||||
|
let pathseg = bezier_to_path_seg(bezier).to_cubic();
|
||||||
|
let mut start_t = 0.;
|
||||||
|
|
||||||
|
for split_t in find_splits(pathseg, direction) {
|
||||||
|
let [first, second] = [pathseg.subsegment(start_t..split_t), pathseg.subsegment(split_t..1.)];
|
||||||
|
let [first_handles, second_handles] = [first, second].map(cubic_to_handles);
|
||||||
|
let middle_point = next_point.next_id();
|
||||||
|
let start_segment = next_segment.next_id();
|
||||||
|
|
||||||
|
let middle_point_index = vector.point_domain.len();
|
||||||
|
vector.point_domain.push(middle_point, DVec2::new(first.end().x, first.end().y));
|
||||||
|
vector.segment_domain.push(start_segment, start_index, middle_point_index, first_handles, StrokeId::ZERO);
|
||||||
|
vector.segment_domain.set_start_point(segment_index, middle_point_index);
|
||||||
|
vector.segment_domain.set_handles(segment_index, second_handles);
|
||||||
|
|
||||||
|
start_t = split_t;
|
||||||
|
start_index = middle_point_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy all segments with the offset of `direction`.
|
||||||
|
fn offset_copy_all_segments(vector: &mut graphic_types::Vector, direction: DVec2) {
|
||||||
|
let points_count = vector.point_domain.ids().len();
|
||||||
|
let mut next_point = vector.point_domain.next_id();
|
||||||
|
for index in 0..points_count {
|
||||||
|
vector.point_domain.push(next_point.next_id(), vector.point_domain.positions()[index] + direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment_count = vector.segment_domain.ids().len();
|
||||||
|
let mut next_segment = vector.segment_domain.next_id();
|
||||||
|
for index in 0..segment_count {
|
||||||
|
vector.segment_domain.push(
|
||||||
|
next_segment.next_id(),
|
||||||
|
vector.segment_domain.start_point()[index] + points_count,
|
||||||
|
vector.segment_domain.end_point()[index] + points_count,
|
||||||
|
vector.segment_domain.handles()[index].apply_transformation(|x| x + direction),
|
||||||
|
vector.segment_domain.stroke()[index],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Join points from the original to the copied that are on opposite sides of the direction.
|
||||||
|
fn join_extrema_edges(vector: &mut graphic_types::Vector, direction: DVec2) {
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
enum Found {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Positive,
|
||||||
|
Negative,
|
||||||
|
Both,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Found {
|
||||||
|
fn update(&mut self, value: f64) {
|
||||||
|
*self = match (*self, value > 0.) {
|
||||||
|
(Found::None, true) => Found::Positive,
|
||||||
|
(Found::None, false) => Found::Negative,
|
||||||
|
(Found::Positive, true) | (Found::Negative, false) => Found::Both,
|
||||||
|
_ => Found::Invalid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_half_points = vector.point_domain.len() / 2;
|
||||||
|
let mut points = vec![Found::None; first_half_points];
|
||||||
|
let first_half_segments = vector.segment_domain.ids().len() / 2;
|
||||||
|
|
||||||
|
for segment_id in 0..first_half_segments {
|
||||||
|
let index = [vector.segment_domain.start_point()[segment_id], vector.segment_domain.end_point()[segment_id]];
|
||||||
|
let position = index.map(|index| vector.point_domain.positions()[index]);
|
||||||
|
|
||||||
|
if position[0].abs_diff_eq(position[1], 1e-6) {
|
||||||
|
continue; // Skip zero length segments
|
||||||
|
}
|
||||||
|
|
||||||
|
points[index[0]].update(direction.perp_dot(position[1] - position[0]));
|
||||||
|
points[index[1]].update(direction.perp_dot(position[0] - position[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_segment = vector.segment_domain.next_id();
|
||||||
|
for (index, &point) in points.iter().enumerate().take(first_half_points) {
|
||||||
|
if point != Found::Both {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector
|
||||||
|
.segment_domain
|
||||||
|
.push(next_segment.next_id(), index, index + first_half_points, BezierHandles::Linear, StrokeId::ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Join all points from the original to the copied.
|
||||||
|
fn join_all(vector: &mut graphic_types::Vector) {
|
||||||
|
let mut next_segment = vector.segment_domain.next_id();
|
||||||
|
let first_half = vector.point_domain.len() / 2;
|
||||||
|
for index in 0..first_half {
|
||||||
|
vector.segment_domain.push(next_segment.next_id(), index, index + first_half, BezierHandles::Linear, StrokeId::ZERO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extrude(vector: &mut graphic_types::Vector, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) {
|
||||||
|
split(vector, direction);
|
||||||
|
offset_copy_all_segments(vector, direction);
|
||||||
|
|
||||||
|
match joining_algorithm {
|
||||||
|
ExtrudeJoiningAlgorithm::Extrema => join_extrema_edges(vector, direction),
|
||||||
|
ExtrudeJoiningAlgorithm::All => join_all(vector),
|
||||||
|
ExtrudeJoiningAlgorithm::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod extrude_tests {
|
||||||
|
use glam::DVec2;
|
||||||
|
use kurbo::{ParamCurve, ParamCurveDeriv};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_cubic() {
|
||||||
|
let l1 = kurbo::CubicBez::new((0., 0.), (100., 0.), (100., 100.), (0., 100.));
|
||||||
|
assert_eq!(super::find_splits(l1, DVec2::Y).collect::<Vec<f64>>(), vec![0.5]);
|
||||||
|
assert!(super::find_splits(l1, DVec2::X).collect::<Vec<f64>>().is_empty());
|
||||||
|
|
||||||
|
let l2 = kurbo::CubicBez::new((0., 0.), (0., 0.), (100., 0.), (100., 0.));
|
||||||
|
assert!(super::find_splits(l2, DVec2::X).collect::<Vec<f64>>().is_empty());
|
||||||
|
|
||||||
|
let l3 = kurbo::PathSeg::Line(kurbo::Line::new((0., 0.), (100., 0.)));
|
||||||
|
assert!(super::find_splits(l3.to_cubic(), DVec2::X).collect::<Vec<f64>>().is_empty());
|
||||||
|
|
||||||
|
let l4 = kurbo::CubicBez::new((0., 0.), (100., -10.), (100., 110.), (0., 100.));
|
||||||
|
let splits = super::find_splits(l4, DVec2::X).map(|t| l4.deriv().eval(t)).collect::<Vec<_>>();
|
||||||
|
assert_eq!(splits.len(), 2);
|
||||||
|
assert!(splits.iter().all(|&deriv| deriv.y.abs() < 1e-8), "{splits:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_vector() {
|
||||||
|
let curve = kurbo::PathSeg::Cubic(kurbo::CubicBez::new((0., 0.), (100., -10.), (100., 110.), (0., 100.)));
|
||||||
|
let mut vector = graphic_types::Vector::from_bezpath(kurbo::BezPath::from_path_segments([curve].into_iter()));
|
||||||
|
super::split(&mut vector, DVec2::X);
|
||||||
|
assert_eq!(vector.segment_ids().len(), 3);
|
||||||
|
assert_eq!(vector.point_domain.ids().len(), 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
|
async fn extrude(_: impl Ctx, mut source: Table<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> Table<Vector> {
|
||||||
|
for TableRowMut { element: source, .. } in source.iter_mut() {
|
||||||
|
extrude_algorithms::extrude(source, direction, joining_algorithm);
|
||||||
|
}
|
||||||
|
source
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Table<Vector>) -> Table<Vector> {
|
async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Table<Vector>) -> Table<Vector> {
|
||||||
let Some((target, target_transform)) = rectangle.get(0).map(|rect| (rect.element, rect.transform)) else {
|
let Some((target, target_transform)) = rectangle.get(0).map(|rect| (rect.element, rect.transform)) else {
|
||||||
|
|
@ -1631,7 +1835,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
||||||
let target_segment_len = target_bezpath.segments().count();
|
let target_segment_len = target_bezpath.segments().count();
|
||||||
let source_segment_len = source_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.
|
// Insert new segments to align the number of segments in source_bezpath and target_bezpath.
|
||||||
make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len);
|
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);
|
make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len);
|
||||||
|
|
||||||
|
|
@ -1736,7 +1940,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||||
segments_connected_count[point_index] += 1;
|
segments_connected_count[point_index] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero out points without exactly two connectors. These are ignored
|
// Zero out points without exactly two connectors. These are ignored.
|
||||||
for count in &mut segments_connected_count {
|
for count in &mut segments_connected_count {
|
||||||
if *count != 2 {
|
if *count != 2 {
|
||||||
*count = 0;
|
*count = 0;
|
||||||
|
|
@ -1764,7 +1968,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_distance_to_spilt(bezier1: PathSeg, bezier2: PathSeg, bevel_length: f64) -> f64 {
|
fn calculate_distance_to_split(bezier1: PathSeg, bezier2: PathSeg, bevel_length: f64) -> f64 {
|
||||||
if is_linear(bezier1) && is_linear(bezier2) {
|
if is_linear(bezier1) && is_linear(bezier2) {
|
||||||
let v1 = (bezier1.end() - bezier1.start()).normalize();
|
let v1 = (bezier1.end() - bezier1.start()).normalize();
|
||||||
let v2 = (bezier1.end() - bezier2.end()).normalize();
|
let v2 = (bezier1.end() - bezier2.end()).normalize();
|
||||||
|
|
@ -1901,7 +2105,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||||
let mut next_bezier = handles_to_segment(next_start, *next_handles, next_end);
|
let mut next_bezier = handles_to_segment(next_start, *next_handles, next_end);
|
||||||
next_bezier = Affine::new(transform.to_cols_array()) * next_bezier;
|
next_bezier = Affine::new(transform.to_cols_array()) * next_bezier;
|
||||||
|
|
||||||
let spilt_distance = calculate_distance_to_spilt(bezier, next_bezier, distance);
|
let calculated_split_distance = calculate_distance_to_split(bezier, next_bezier, distance);
|
||||||
|
|
||||||
if is_linear(bezier) {
|
if is_linear(bezier) {
|
||||||
bezier = PathSeg::Line(Line::new(bezier.start(), bezier.end()));
|
bezier = PathSeg::Line(Line::new(bezier.start(), bezier.end()));
|
||||||
|
|
@ -1934,7 +2138,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||||
let valid_length = length > 1e-10;
|
let valid_length = length > 1e-10;
|
||||||
if segments_connected[*end_point] > 0 && valid_length {
|
if segments_connected[*end_point] > 0 && valid_length {
|
||||||
// Apply the bevel to the end
|
// Apply the bevel to the end
|
||||||
let distance = spilt_distance.min(original_length.min(next_original_length) / 2.);
|
let distance = calculated_split_distance.min(original_length.min(next_original_length) / 2.);
|
||||||
bezier = split_distance(bezier.reverse(), distance, length).reverse();
|
bezier = split_distance(bezier.reverse(), distance, length).reverse();
|
||||||
|
|
||||||
if index == 0 && next_index == 1 {
|
if index == 0 && next_index == 1 {
|
||||||
|
|
@ -1953,7 +2157,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||||
let valid_length = next_length > 1e-10;
|
let valid_length = next_length > 1e-10;
|
||||||
if segments_connected[*next_start_point] > 0 && valid_length {
|
if segments_connected[*next_start_point] > 0 && valid_length {
|
||||||
// Apply the bevel to the start
|
// Apply the bevel to the start
|
||||||
let distance = spilt_distance.min(next_original_length.min(original_length) / 2.);
|
let distance = calculated_split_distance.min(next_original_length.min(original_length) / 2.);
|
||||||
next_bezier = split_distance(next_bezier, distance, next_length);
|
next_bezier = split_distance(next_bezier, distance, next_length);
|
||||||
next_length = (next_length - distance).max(0.);
|
next_length = (next_length - distance).max(0.);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue