mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Merge 6ca0d17571
into 24c6281644
This commit is contained in:
commit
d6bcdee482
5 changed files with 294 additions and 63 deletions
|
@ -1,7 +1,8 @@
|
|||
use super::poisson_disk::poisson_disk_sample;
|
||||
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::vector::misc::{PointSpacingType, dvec2_to_point};
|
||||
use glam::DVec2;
|
||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape};
|
||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
|
||||
|
||||
/// Splits the [`BezPath`] at `t` value which lie in the range of [0, 1].
|
||||
/// Returns [`None`] if the given [`BezPath`] has no segments or `t` is within f64::EPSILON of 0 or 1.
|
||||
|
@ -314,3 +315,16 @@ pub fn poisson_disk_points(bezpath_index: usize, bezpaths: &[(BezPath, Rect)], s
|
|||
|
||||
poisson_disk_sample(offset, width, height, separation_disk_diameter, point_in_shape_checker, line_intersect_shape_checker, rng)
|
||||
}
|
||||
|
||||
/// Returns true if the Bezier curve is equivalent to a line.
|
||||
///
|
||||
/// **NOTE**: This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line.
|
||||
pub fn is_linear(segment: &PathSeg) -> bool {
|
||||
let is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE };
|
||||
|
||||
match *segment {
|
||||
PathSeg::Line(_) => true,
|
||||
PathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2),
|
||||
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::Point;
|
||||
use kurbo::{CubicBez, Line, PathSeg, Point, QuadBez};
|
||||
|
||||
/// Represents different ways of calculating the centroid.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||
|
@ -96,3 +97,33 @@ pub fn point_to_dvec2(point: Point) -> DVec2 {
|
|||
pub fn dvec2_to_point(value: DVec2) -> Point {
|
||||
Point { x: value.x, y: value.y }
|
||||
}
|
||||
|
||||
pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
|
||||
match *segment {
|
||||
PathSeg::Line(_) => BezierHandles::Linear,
|
||||
PathSeg::Quad(QuadBez { p0: _, p1, p2: _ }) => BezierHandles::Quadratic { handle: point_to_dvec2(p1) },
|
||||
PathSeg::Cubic(CubicBez { p0: _, p1, p2, p3: _ }) => BezierHandles::Cubic {
|
||||
handle_start: point_to_dvec2(p1),
|
||||
handle_end: point_to_dvec2(p2),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle);
|
||||
let p2 = dvec2_to_point(end);
|
||||
PathSeg::Quad(QuadBez::new(p0, p1, p2))
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle_start);
|
||||
let p2 = dvec2_to_point(handle_end);
|
||||
let p3 = dvec2_to_point(end);
|
||||
PathSeg::Cubic(CubicBez::new(p0, p1, p2, p3))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,10 +226,10 @@ impl VectorData {
|
|||
|
||||
pub fn close_subpaths(&mut self) {
|
||||
let segments_to_add: Vec<_> = self
|
||||
.stroke_bezier_paths()
|
||||
.filter(|subpath| !subpath.closed)
|
||||
.filter_map(|subpath| {
|
||||
let (first, last) = subpath.manipulator_groups().first().zip(subpath.manipulator_groups().last())?;
|
||||
.build_stroke_path_iter()
|
||||
.filter(|(_, closed)| !closed)
|
||||
.filter_map(|(manipulator_groups, _)| {
|
||||
let (first, last) = manipulator_groups.first().zip(manipulator_groups.last())?;
|
||||
let (start, end) = self.point_domain.resolve_id(first.id).zip(self.point_domain.resolve_id(last.id))?;
|
||||
Some((start, end))
|
||||
})
|
||||
|
@ -370,7 +370,7 @@ impl VectorData {
|
|||
}
|
||||
|
||||
pub fn check_point_inside_shape(&self, vector_data_transform: DAffine2, point: DVec2) -> bool {
|
||||
let bez_paths: Vec<_> = self
|
||||
let number = self
|
||||
.stroke_bezpath_iter()
|
||||
.map(|mut bezpath| {
|
||||
// TODO: apply transform to points instead of modifying the paths
|
||||
|
@ -379,19 +379,9 @@ impl VectorData {
|
|||
let bbox = bezpath.bounding_box();
|
||||
(bezpath, bbox)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check against all paths the point is contained in to compute the correct winding number
|
||||
let mut number = 0;
|
||||
|
||||
for (shape, bbox) in bez_paths {
|
||||
if bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y {
|
||||
continue;
|
||||
}
|
||||
|
||||
let winding = shape.winding(dvec2_to_point(point));
|
||||
number += winding;
|
||||
}
|
||||
.filter(|(_, bbox)| bbox.contains(dvec2_to_point(point)))
|
||||
.map(|(bezpath, _)| bezpath.winding(dvec2_to_point(point)))
|
||||
.sum::<i32>();
|
||||
|
||||
// Non-zero fill rule
|
||||
number != 0
|
||||
|
|
|
@ -440,6 +440,35 @@ impl SegmentDomain {
|
|||
let handles = self.handles.iter_mut();
|
||||
zip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))
|
||||
}
|
||||
|
||||
pub(crate) fn pair_handles_and_points_mut_by_index(
|
||||
&mut self,
|
||||
index1: usize,
|
||||
index2: usize,
|
||||
) -> (&mut bezier_rs::BezierHandles, &mut usize, &mut usize, &mut bezier_rs::BezierHandles, &mut usize, &mut usize) {
|
||||
// Use split_at_mut to avoid multiple mutable borrows of the same slice
|
||||
let (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));
|
||||
let (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));
|
||||
let (end_first, end_second) = self.end_point.split_at_mut(index2.max(index1));
|
||||
|
||||
let (h1, h2) = if index1 < index2 {
|
||||
(&mut handles_first[index1], &mut handles_second[0])
|
||||
} else {
|
||||
(&mut handles_second[0], &mut handles_first[index2])
|
||||
};
|
||||
let (sp1, sp2) = if index1 < index2 {
|
||||
(&mut start_first[index1], &mut start_second[0])
|
||||
} else {
|
||||
(&mut start_second[0], &mut start_first[index2])
|
||||
};
|
||||
let (ep1, ep2) = if index1 < index2 {
|
||||
(&mut end_first[index1], &mut end_second[0])
|
||||
} else {
|
||||
(&mut end_second[0], &mut end_first[index2])
|
||||
};
|
||||
|
||||
(h1, sp1, ep1, h2, sp2, ep2)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
@ -9,19 +9,23 @@ use crate::instances::{Instance, InstanceMut, Instances};
|
|||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
||||
use crate::transform::{Footprint, ReferencePoint, Transform};
|
||||
use crate::vector::PointDomain;
|
||||
use crate::vector::algorithms::bezpath_algorithms::{eval_pathseg_euclidean, is_linear};
|
||||
use crate::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType};
|
||||
use crate::vector::misc::{handles_to_segment, segment_to_handles};
|
||||
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use crate::vector::{FillId, PointDomain, RegionId};
|
||||
use crate::vector::{FillId, RegionId};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
use bezier_rs::{Join, ManipulatorGroup, Subpath};
|
||||
|
||||
use bezier_rs::{BezierHandles, Join, ManipulatorGroup, Subpath};
|
||||
use core::f64::consts::PI;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::f64::consts::PI;
|
||||
use std::f64::consts::TAU;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// Implemented for types that can be converted to an iterator of vector data.
|
||||
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
|
||||
|
@ -1737,10 +1741,9 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
|
|||
|
||||
fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData {
|
||||
// Splits a bézier curve based on a distance measurement
|
||||
fn split_distance(bezier: bezier_rs::Bezier, distance: f64, length: f64) -> bezier_rs::Bezier {
|
||||
const EUCLIDEAN_ERROR: f64 = 0.001;
|
||||
let parametric = bezier.euclidean_to_parametric_with_total_length((distance / length).clamp(0., 1.), EUCLIDEAN_ERROR, length);
|
||||
bezier.split(bezier_rs::TValue::Parametric(parametric))[1]
|
||||
fn split_distance(bezier: PathSeg, distance: f64, length: f64) -> PathSeg {
|
||||
let parametric = eval_pathseg_euclidean(bezier, (distance / length).clamp(0., 1.), DEFAULT_ACCURACY);
|
||||
bezier.subsegment(parametric..1.)
|
||||
}
|
||||
|
||||
/// Produces a list that corresponds with the point ID. The value is how many segments are connected.
|
||||
|
@ -1779,65 +1782,229 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
|
|||
}
|
||||
}
|
||||
|
||||
fn calculate_distance_to_spilt(bezier1: PathSeg, bezier2: PathSeg, bevel_length: f64) -> f64 {
|
||||
if is_linear(&bezier1) && is_linear(&bezier2) {
|
||||
let v1 = (bezier1.end() - bezier1.start()).normalize();
|
||||
let v2 = (bezier1.end() - bezier2.end()).normalize();
|
||||
|
||||
let dot_product = v1.dot(v2);
|
||||
|
||||
let angle_rad = dot_product.acos();
|
||||
|
||||
bevel_length / ((angle_rad / 2.0).sin())
|
||||
} else {
|
||||
let length1 = bezier1.perimeter(DEFAULT_ACCURACY);
|
||||
let length2 = bezier2.perimeter(DEFAULT_ACCURACY);
|
||||
|
||||
let max_split = length1.min(length2);
|
||||
|
||||
let mut split_distance = 0.0;
|
||||
let mut best_diff = f64::MAX;
|
||||
let mut current_best_distance = 0.0;
|
||||
|
||||
let clamp_and_round = |value: f64| ((value * 1000.0).round() / 1000.0).clamp(0.0, 1.0);
|
||||
|
||||
const INITIAL_SAMPLES: usize = 50;
|
||||
for i in 0..=INITIAL_SAMPLES {
|
||||
let distance_sample = max_split * (i as f64 / INITIAL_SAMPLES as f64);
|
||||
|
||||
let x_point_t = eval_pathseg_euclidean(bezier1, 1.0 - clamp_and_round(distance_sample / length1), DEFAULT_ACCURACY);
|
||||
let y_point_t = eval_pathseg_euclidean(bezier2, clamp_and_round(distance_sample / length2), DEFAULT_ACCURACY);
|
||||
|
||||
let x_point = bezier1.eval(x_point_t);
|
||||
let y_point = bezier2.eval(y_point_t);
|
||||
|
||||
let distance = x_point.distance(y_point);
|
||||
let diff = (bevel_length - distance).abs();
|
||||
|
||||
if diff < best_diff {
|
||||
best_diff = diff;
|
||||
current_best_distance = distance_sample;
|
||||
}
|
||||
|
||||
if bevel_length - distance < 0.0 {
|
||||
split_distance = distance_sample;
|
||||
|
||||
if i > 0 {
|
||||
let prev_sample = max_split * ((i - 1) as f64 / INITIAL_SAMPLES as f64);
|
||||
|
||||
const REFINE_STEPS: usize = 10;
|
||||
for j in 1..=REFINE_STEPS {
|
||||
let refined_sample = prev_sample + (distance_sample - prev_sample) * (j as f64 / REFINE_STEPS as f64);
|
||||
|
||||
let x_point_t = eval_pathseg_euclidean(bezier1, 1.0 - (refined_sample / length1).clamp(0.0, 1.0), DEFAULT_ACCURACY);
|
||||
let y_point_t = eval_pathseg_euclidean(bezier2, (refined_sample / length2).clamp(0.0, 1.0), DEFAULT_ACCURACY);
|
||||
|
||||
let x_point = bezier1.eval(x_point_t);
|
||||
let y_point = bezier2.eval(y_point_t);
|
||||
|
||||
let distance = x_point.distance(y_point);
|
||||
|
||||
if bevel_length - distance < 0.0 {
|
||||
split_distance = refined_sample;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if split_distance == 0.0 && current_best_distance > 0.0 {
|
||||
split_distance = current_best_distance;
|
||||
}
|
||||
|
||||
split_distance
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_segments(segment_domain: &SegmentDomain) -> Vec<usize> {
|
||||
let start_points = segment_domain.start_point();
|
||||
let end_points = segment_domain.end_point();
|
||||
|
||||
let mut sorted_segments = Vec::new();
|
||||
|
||||
sorted_segments.push(0);
|
||||
|
||||
for _ in 0..start_points.len() {
|
||||
match sorted_segments.last() {
|
||||
Some(last) => {
|
||||
if let Some(index) = start_points.iter().position(|&p| p == end_points[*last as usize]) {
|
||||
if index == 0 {
|
||||
break;
|
||||
} else {
|
||||
sorted_segments.push(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
sorted_segments
|
||||
}
|
||||
|
||||
fn update_existing_segments(vector_data: &mut VectorData, vector_data_transform: DAffine2, distance: f64, segments_connected: &mut [usize]) -> Vec<[usize; 2]> {
|
||||
let mut next_id = vector_data.point_domain.next_id();
|
||||
let mut new_segments = Vec::new();
|
||||
|
||||
for (handles, start_point_index, end_point_index) in vector_data.segment_domain.handles_and_points_mut() {
|
||||
// Convert the original segment to a bezier
|
||||
let mut bezier = bezier_rs::Bezier {
|
||||
start: vector_data.point_domain.positions()[*start_point_index],
|
||||
end: vector_data.point_domain.positions()[*end_point_index],
|
||||
handles: *handles,
|
||||
};
|
||||
let sorted_segments = sort_segments(&vector_data.segment_domain);
|
||||
let segment_domain = &mut vector_data.segment_domain;
|
||||
let segment_domain_length = segment_domain.ids().len();
|
||||
|
||||
if bezier.is_linear() {
|
||||
bezier.handles = bezier_rs::BezierHandles::Linear;
|
||||
let mut first_original_length = 0.;
|
||||
let mut first_length = 0.;
|
||||
let mut prev_original_length = 0.;
|
||||
let mut prev_length = 0.;
|
||||
|
||||
for i in 0..segment_domain_length {
|
||||
let (index, next_index) = if i == segment_domain_length - 1 { (i, 0) } else { (i, i + 1) };
|
||||
|
||||
let (handles, start_point, end_point, next_handles, next_start_point, next_end_point) =
|
||||
segment_domain.pair_handles_and_points_mut_by_index(sorted_segments[index], sorted_segments[next_index]);
|
||||
|
||||
let start = vector_data.point_domain.positions()[*start_point];
|
||||
|
||||
let end = vector_data.point_domain.positions()[*end_point];
|
||||
|
||||
let mut bezier = handles_to_segment(start, *handles, end);
|
||||
|
||||
bezier = Affine::new(vector_data_transform.to_cols_array()) * bezier;
|
||||
|
||||
let next_start = vector_data.point_domain.positions()[*next_start_point];
|
||||
let next_end = vector_data.point_domain.positions()[*next_end_point];
|
||||
|
||||
let mut next_bezier = handles_to_segment(next_start, *next_handles, next_end);
|
||||
|
||||
next_bezier = Affine::new(vector_data_transform.to_cols_array()) * next_bezier;
|
||||
|
||||
let spilt_distance = calculate_distance_to_spilt(bezier, next_bezier, distance);
|
||||
|
||||
if is_linear(&bezier) {
|
||||
let start = point_to_dvec2(bezier.start());
|
||||
let end = point_to_dvec2(bezier.end());
|
||||
bezier = handles_to_segment(start, BezierHandles::Linear, end);
|
||||
}
|
||||
bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p));
|
||||
|
||||
if is_linear(&next_bezier) {
|
||||
let start = point_to_dvec2(next_bezier.start());
|
||||
let end = point_to_dvec2(next_bezier.end());
|
||||
next_bezier = handles_to_segment(start, BezierHandles::Linear, end);
|
||||
}
|
||||
|
||||
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
|
||||
|
||||
let original_length = bezier.length(None);
|
||||
let mut length = original_length;
|
||||
|
||||
// Only split if the length is big enough to make it worthwhile
|
||||
let valid_length = length > 1e-10;
|
||||
if segments_connected[*start_point_index] > 0 && valid_length {
|
||||
// Apply the bevel to the start
|
||||
let distance = distance.min(original_length / 2.);
|
||||
bezier = split_distance(bezier, distance, length);
|
||||
length = (length - distance).max(0.);
|
||||
// Update the start position
|
||||
let pos = inverse_transform.transform_point2(bezier.start);
|
||||
create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, start_point_index, &mut next_id, &mut new_segments);
|
||||
if index == 0 && next_index == 1 {
|
||||
first_original_length = bezier.perimeter(DEFAULT_ACCURACY);
|
||||
first_length = first_original_length;
|
||||
}
|
||||
|
||||
let (original_length, length) = if index == 0 {
|
||||
(bezier.perimeter(DEFAULT_ACCURACY), bezier.perimeter(DEFAULT_ACCURACY))
|
||||
} else {
|
||||
(prev_original_length, prev_length)
|
||||
};
|
||||
|
||||
let (next_original_length, mut next_length) = if index == segment_domain_length - 1 && next_index == 0 {
|
||||
(first_original_length, first_length)
|
||||
} else {
|
||||
(next_bezier.perimeter(DEFAULT_ACCURACY), next_bezier.perimeter(DEFAULT_ACCURACY))
|
||||
};
|
||||
|
||||
// Only split if the length is big enough to make it worthwhile
|
||||
let valid_length = length > 1e-10;
|
||||
if segments_connected[*end_point_index] > 0 && valid_length {
|
||||
if segments_connected[*end_point] > 0 && valid_length {
|
||||
// Apply the bevel to the end
|
||||
let distance = distance.min(original_length / 2.);
|
||||
bezier = split_distance(bezier.reversed(), distance, length).reversed();
|
||||
let distance = spilt_distance.min(original_length.min(next_original_length) / 2.);
|
||||
bezier = split_distance(bezier.reverse(), distance, length).reverse();
|
||||
|
||||
if index == 0 && next_index == 1 {
|
||||
first_length = (length - distance).max(0.);
|
||||
}
|
||||
|
||||
// Update the end position
|
||||
let pos = inverse_transform.transform_point2(bezier.end);
|
||||
create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, end_point_index, &mut next_id, &mut new_segments);
|
||||
let pos = inverse_transform.transform_point2(point_to_dvec2(bezier.end()));
|
||||
create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, end_point, &mut next_id, &mut new_segments);
|
||||
}
|
||||
// Update the handles
|
||||
*handles = bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p));
|
||||
*handles = segment_to_handles(&bezier).apply_transformation(|p| inverse_transform.transform_point2(p));
|
||||
|
||||
// Only split if the length is big enough to make it worthwhile
|
||||
let valid_length = next_length > 1e-10;
|
||||
if segments_connected[*next_start_point] > 0 && valid_length {
|
||||
// Apply the bevel to the start
|
||||
let distance = spilt_distance.min(next_original_length.min(original_length) / 2.);
|
||||
next_bezier = split_distance(next_bezier, distance, next_length);
|
||||
next_length = (next_length - distance).max(0.);
|
||||
// Update the start position
|
||||
let pos = inverse_transform.transform_point2(point_to_dvec2(next_bezier.start()));
|
||||
|
||||
create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, next_start_point, &mut next_id, &mut new_segments);
|
||||
|
||||
// Update the handles
|
||||
*next_handles = segment_to_handles(&next_bezier).apply_transformation(|p| inverse_transform.transform_point2(p));
|
||||
}
|
||||
|
||||
prev_original_length = next_original_length;
|
||||
prev_length = next_length;
|
||||
}
|
||||
new_segments
|
||||
}
|
||||
|
||||
fn insert_new_segments(vector_data: &mut VectorData, new_segments: &[[usize; 2]]) {
|
||||
let mut next_id = vector_data.segment_domain.next_id();
|
||||
|
||||
for &[start, end] in new_segments {
|
||||
vector_data.segment_domain.push(next_id.next_id(), start, end, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
let handles = bezier_rs::BezierHandles::Linear;
|
||||
vector_data.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
let mut segments_connected = segments_connected_count(&vector_data);
|
||||
let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected);
|
||||
insert_new_segments(&mut vector_data, &new_segments);
|
||||
if distance > 1.0 {
|
||||
let mut segments_connected = segments_connected_count(&vector_data);
|
||||
let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected);
|
||||
insert_new_segments(&mut vector_data, &new_segments);
|
||||
}
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
@ -1849,7 +2016,7 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
|
|||
for source_instance in source.instance_iter() {
|
||||
result_table.push(Instance {
|
||||
instance: bevel_algorithm(source_instance.instance, source_instance.transform, distance),
|
||||
..Default::default()
|
||||
..source_instance
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1905,7 +2072,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
|
|||
.instance_ref_iter()
|
||||
.map(|vector_data_instance| {
|
||||
let scale = vector_data_instance.transform.decompose_scale();
|
||||
vector_data_instance.instance.stroke_bezier_paths().map(|subpath| subpath.area(Some(1e-3), Some(1e-3))).sum::<f64>() * scale.x * scale.y
|
||||
vector_data_instance.instance.stroke_bezpath_iter().map(|subpath| subpath.area() * scale.x * scale.y).sum::<f64>()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue