mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-11 00:20:56 +08:00
Convert the per-lane vector geometry nodes and the level measures to record kernels
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
use core_types::attribute::{Attr, EditorLayerPath, RemoveAttr};
|
||||
use core_types::attribute::{Attr, EditorLayerPath, RemoveAttr, Transform as TransformAttr};
|
||||
use core_types::gpoll::{GraphError, Interrupt};
|
||||
use core_types::list::List;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_TRANSFORM, Ctx, ExtractIndex, InjectIndex};
|
||||
use core_types::{Ctx, ExtractIndex, InjectIndex};
|
||||
use glam::DAffine2;
|
||||
use graphic_types::Vector;
|
||||
use vector_types::markers::EditorClickTarget;
|
||||
@@ -41,16 +40,12 @@ fn path_modify<'e>(
|
||||
|
||||
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.
|
||||
#[node_macro::node(category("Vector"))]
|
||||
fn apply_transform(_ctx: impl Ctx, mut vector: List<Vector>) -> List<Vector> {
|
||||
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
||||
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
|
||||
for (_, point) in element.point_domain.positions_mut() {
|
||||
fn apply_transform(_ctx: impl Ctx, (mut vector, transform): (Vector, Attr<TransformAttr>)) -> (Vector, Attr<TransformAttr>) {
|
||||
let transform: DAffine2 = *transform;
|
||||
for (_, point) in vector.point_domain.positions_mut() {
|
||||
*point = transform.transform_point2(*point);
|
||||
}
|
||||
element.segment_domain.transform(*transform);
|
||||
vector.segment_domain.transform(transform);
|
||||
|
||||
*transform = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
vector
|
||||
(vector, Attr(DAffine2::IDENTITY))
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use core::cmp::Ordering;
|
||||
use core::f64::consts::{PI, TAU};
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core_types::attribute::Transform as TransformAttr;
|
||||
use core_types::attribute::{Attr, BlendMode as BlendModeAttr, ClippingMask, EditorLayerPath, Opacity, OpacityFill};
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::gpoll::Interrupt;
|
||||
use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
|
||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::attribute::{Attr, BlendMode as BlendModeAttr, ClippingMask, EditorLayerPath, Opacity, OpacityFill};
|
||||
use core_types::context::IndexLink;
|
||||
use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
|
||||
use core_types::gpoll::{Extent, GPoll};
|
||||
use core_types::gpoll::GraphError;
|
||||
use core_types::gpoll::Interrupt;
|
||||
use core_types::gpoll::{Extent, GPoll};
|
||||
use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
|
||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||
use core_types::transform::Transform;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
||||
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
||||
use core_types::attribute::Transform as TransformAttr;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
|
||||
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
|
||||
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
|
||||
@@ -27,7 +27,6 @@ use rand::{Rng, SeedableRng};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use vector_types::gradient::{build_transform_with_y_preservation, initial_gradient_transform_for_bounding_box};
|
||||
use vector_types::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
||||
use vector_types::vector::PointDomain;
|
||||
use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath};
|
||||
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
@@ -39,6 +38,7 @@ use vector_types::vector::misc::{
|
||||
};
|
||||
use vector_types::vector::style::{GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||
use vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
||||
use vector_types::{GradientSpreadMethod, GradientType};
|
||||
|
||||
/// The standard row attributes a per-lane re-emission carries from its
|
||||
@@ -101,9 +101,7 @@ fn assign_colors<'e>(
|
||||
return Err(GraphError::past_end().into());
|
||||
}
|
||||
let element = content.element_ref(lane).clone();
|
||||
let park_existing = |paint: Option<&List<Graphic>>| -> Result<Option<&'e List<Graphic>>, Interrupt> {
|
||||
paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose()
|
||||
};
|
||||
let park_existing = |paint: Option<&List<Graphic>>| -> Result<Option<&'e List<Graphic>>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() };
|
||||
let existing_fill = park_existing(content.lane(lane).attr::<Fill>())?;
|
||||
let existing_stroke = park_existing(content.lane(lane).attr::<StrokeAttr>())?;
|
||||
let carried = carried_lane_attrs(ctx.arena(), content.lane(lane))?;
|
||||
@@ -161,8 +159,7 @@ fn assign_colors_extent(
|
||||
fn assign_colors_graphic<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
content: IList<Graphic>,
|
||||
#[default(true)]
|
||||
fill: bool,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
gradient: IList<GradientStops>,
|
||||
reverse: bool,
|
||||
@@ -316,8 +313,7 @@ fn fill<'e>(
|
||||
fn fill_graphic_leveled<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
(element, _content_fill): (Graphic, Attr<'e, Fill>),
|
||||
#[default(Color::BLACK)]
|
||||
fill: IList<Graphic>,
|
||||
#[default(Color::BLACK)] fill: IList<Graphic>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<GradientStops>,
|
||||
_gradient_type: GradientType,
|
||||
@@ -415,20 +411,17 @@ fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Ve
|
||||
fn stroke_graphic_leveled<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
(element, content_transform): (Graphic, Attr<TransformAttr>),
|
||||
#[default(Color::BLACK)]
|
||||
paint: IList<Graphic>,
|
||||
#[default(Color::BLACK)] paint: IList<Graphic>,
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
weight: f64,
|
||||
align: StrokeAlign,
|
||||
cap: StrokeCap,
|
||||
join: StrokeJoin,
|
||||
#[default(4.)]
|
||||
miter_limit: f64,
|
||||
#[default(4.)] miter_limit: f64,
|
||||
paint_order: PaintOrder,
|
||||
dash_lengths: IList<f64>,
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
#[unit(" px")] dash_offset: f64,
|
||||
) -> Result<(Graphic, Attr<TransformAttr>, Attr<'e, StrokeAttr>), Interrupt> {
|
||||
let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
||||
let stroke = Stroke {
|
||||
@@ -570,7 +563,7 @@ fn copy_to_points_extent(
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn round_corners(
|
||||
_: impl Ctx,
|
||||
source: List<Vector>,
|
||||
(source, transform): (Vector, Attr<TransformAttr>),
|
||||
#[hard(0..)]
|
||||
#[default(10.)]
|
||||
radius: PixelLength,
|
||||
@@ -583,13 +576,9 @@ fn round_corners(
|
||||
#[hard(0..180)]
|
||||
#[default(5.)]
|
||||
min_angle_threshold: Angle,
|
||||
) -> List<Vector> {
|
||||
(0..source.len())
|
||||
.map(|index| {
|
||||
let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
let source_transform: DAffine2 = *transform;
|
||||
let source_transform_inverse = source_transform.inverse();
|
||||
let attributes = source.clone_item_attributes(index);
|
||||
let source = source.element(index).unwrap();
|
||||
|
||||
// Flip the roundness to help with user intuition
|
||||
let roundness = 1. - roundness;
|
||||
@@ -674,37 +663,23 @@ fn round_corners(
|
||||
result.append_bezpath(rounded_subpath);
|
||||
}
|
||||
|
||||
Item::from_parts(result, attributes)
|
||||
})
|
||||
.collect()
|
||||
(result, Attr(source_transform))
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Merge by Distance"), category("Vector: Modifier"), path(core_types::vector))]
|
||||
pub fn merge_by_distance(
|
||||
_: impl Ctx,
|
||||
content: List<Vector>,
|
||||
(mut content, transform): (Vector, Attr<TransformAttr>),
|
||||
#[default(0.1)]
|
||||
#[hard(0.0001..)]
|
||||
distance: PixelLength,
|
||||
algorithm: MergeByDistanceAlgorithm,
|
||||
) -> List<Vector> {
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
match algorithm {
|
||||
MergeByDistanceAlgorithm::Spatial => content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
row.element_mut().merge_by_distance_spatial(transform, distance);
|
||||
row
|
||||
})
|
||||
.collect(),
|
||||
MergeByDistanceAlgorithm::Topological => content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
row.element_mut().merge_by_distance_topological(distance);
|
||||
row
|
||||
})
|
||||
.collect(),
|
||||
MergeByDistanceAlgorithm::Spatial => content.merge_by_distance_spatial(*transform, distance),
|
||||
MergeByDistanceAlgorithm::Topological => content.merge_by_distance_topological(distance),
|
||||
}
|
||||
(content, Attr(*transform))
|
||||
}
|
||||
|
||||
pub mod extrude_algorithms {
|
||||
@@ -905,23 +880,19 @@ pub mod extrude_algorithms {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn extrude(_: impl Ctx, mut source: List<Vector>, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> List<Vector> {
|
||||
for vector in source.iter_element_values_mut() {
|
||||
extrude_algorithms::extrude(vector, direction, joining_algorithm);
|
||||
}
|
||||
fn extrude(_: impl Ctx, mut source: Vector, direction: DVec2, joining_algorithm: ExtrudeJoiningAlgorithm) -> Vector {
|
||||
extrude_algorithms::extrude(&mut source, direction, joining_algorithm);
|
||||
source
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector>) -> List<Vector> {
|
||||
let Some(target) = rectangle.element(0).cloned() else { return content };
|
||||
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let vector = std::mem::take(row.element_mut());
|
||||
fn box_warp(_: impl Ctx + ExtractIndex + InjectIndex + Copy, (vector, transform): (Vector, Attr<TransformAttr>), #[expose] rectangle: IList<Vector>) -> (Vector, Attr<TransformAttr>) {
|
||||
if rectangle.is_empty() {
|
||||
return (vector, Attr(*transform));
|
||||
}
|
||||
let target = rectangle.element_ref(0);
|
||||
let target_transform: DAffine2 = rectangle.lane(0).attr::<TransformAttr>();
|
||||
let transform: DAffine2 = *transform;
|
||||
|
||||
// Get the bounding box of the source vector geometry
|
||||
let source_bbox = vector.bounding_box_with_transform(transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
@@ -978,12 +949,8 @@ fn box_warp(_: impl Ctx, content: List<Vector>, #[expose] rectangle: List<Vector
|
||||
|
||||
result.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
// Add this to the `List` and reset the transform since we've applied it directly to the points
|
||||
*row.element_mut() = result;
|
||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY);
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
// Reset the transform since we've applied it directly to the points
|
||||
(result, Attr(DAffine2::IDENTITY))
|
||||
}
|
||||
|
||||
// Interpolate within a quadrilateral using normalized coordinates (0-1)
|
||||
@@ -1121,7 +1088,7 @@ where
|
||||
#[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))]
|
||||
fn auto_tangents(
|
||||
_: impl Ctx,
|
||||
source: List<Vector>,
|
||||
(source, lane_transform): (Vector, Attr<TransformAttr>),
|
||||
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
|
||||
#[default(0.5)]
|
||||
#[range]
|
||||
@@ -1130,12 +1097,8 @@ fn auto_tangents(
|
||||
/// If active, existing non-zero handles won't be affected.
|
||||
#[default(true)]
|
||||
preserve_existing: bool,
|
||||
) -> List<Vector> {
|
||||
(0..source.len())
|
||||
.map(|index| {
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let attributes = source.clone_item_attributes(index);
|
||||
let source = source.element(index).unwrap();
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
let transform: DAffine2 = *lane_transform;
|
||||
|
||||
let mut result = Vector {
|
||||
stroke: source.stroke.clone(),
|
||||
@@ -1267,18 +1230,11 @@ fn auto_tangents(
|
||||
}
|
||||
}
|
||||
|
||||
Item::from_parts(result, attributes)
|
||||
})
|
||||
.collect()
|
||||
(result, Attr(transform))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let vector = std::mem::take(row.element_mut());
|
||||
|
||||
fn bounding_box(_: impl Ctx, vector: Vector) -> Vector {
|
||||
let mut result = vector
|
||||
.bounding_box_rect()
|
||||
.map(|bbox| {
|
||||
@@ -1291,16 +1247,13 @@ fn bounding_box(_: impl Ctx, content: List<Vector>) -> List<Vector> {
|
||||
result.stroke = vector.stroke.clone();
|
||||
result.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
*row.element_mut() = result;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
|
||||
fn dimensions(_: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Vector>) -> DVec2 {
|
||||
(0..content.len())
|
||||
.filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index)))
|
||||
.filter_map(|index| content.element_ref(index).bounding_box_with_transform(content.lane(index).attr::<TransformAttr>()))
|
||||
.reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)])
|
||||
.map(|[top_left, bottom_right]| bottom_right - top_left)
|
||||
.unwrap_or_default()
|
||||
@@ -1308,18 +1261,17 @@ fn dimensions(_: impl Ctx, content: List<Vector>) -> DVec2 {
|
||||
|
||||
/// Type-asserts a value to be vector data.
|
||||
#[node_macro::node(category("Vector"), name("As Vector"), path(core_types::vector))]
|
||||
fn as_vector(_: impl Ctx, value: List<Vector>) -> List<Vector> {
|
||||
fn as_vector(_: impl Ctx, value: Vector) -> Vector {
|
||||
value
|
||||
}
|
||||
|
||||
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
||||
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(core_types::vector))]
|
||||
fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(true)] closed: bool) -> List<Vector> {
|
||||
for vector in points.iter_element_values_mut() {
|
||||
fn points_to_polyline(_: impl Ctx, mut points: Vector, #[default(true)] closed: bool) -> Vector {
|
||||
let mut segment_domain = SegmentDomain::new();
|
||||
let mut next_id = SegmentId::ZERO;
|
||||
|
||||
let points_count = vector.point_domain.ids().len();
|
||||
let points_count = points.point_domain.ids().len();
|
||||
|
||||
if points_count >= 2 {
|
||||
(0..points_count - 1).for_each(|i| {
|
||||
@@ -1329,26 +1281,21 @@ fn points_to_polyline(_: impl Ctx, mut points: List<Vector>, #[default(true)] cl
|
||||
if closed && points_count != 2 {
|
||||
segment_domain.push(next_id.next_id(), points_count - 1, 0, BezierHandles::Linear, StrokeId::generate());
|
||||
|
||||
vector
|
||||
points
|
||||
.region_domain
|
||||
.push(RegionId::generate(), segment_domain.ids()[0]..=*segment_domain.ids().last().unwrap(), FillId::generate());
|
||||
}
|
||||
}
|
||||
|
||||
vector.segment_domain = segment_domain;
|
||||
}
|
||||
points.segment_domain = segment_domain;
|
||||
|
||||
points
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("offset_path_properties"))]
|
||||
fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
fn offset_path(_: impl Ctx, (vector, lane_transform): (Vector, Attr<TransformAttr>), distance: f64, join: StrokeJoin, #[default(4.)] miter_limit: f64) -> (Vector, Attr<TransformAttr>) {
|
||||
let transform_attribute: DAffine2 = *lane_transform;
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let vector = std::mem::take(row.element_mut());
|
||||
|
||||
let bezpaths = vector.stroke_bezpath_iter();
|
||||
let mut result = Vector {
|
||||
@@ -1379,10 +1326,7 @@ fn offset_path(_: impl Ctx, content: List<Vector>, distance: f64, join: StrokeJo
|
||||
result.append_bezpath(bezpath_out);
|
||||
}
|
||||
|
||||
*row.element_mut() = result;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
(result, Attr(transform_attribute))
|
||||
}
|
||||
|
||||
fn solidify_stroke_core(graphic_list: List<Graphic>) -> List<Vector> {
|
||||
@@ -1610,10 +1554,12 @@ fn solidify_stroke<'e>(
|
||||
/// count as a lower bound and consumers drain to the past-end signal.
|
||||
fn solidify_stroke_extent(content: ListIn<'_, Graphic>, level: LevelIn) -> GPoll<Extent> {
|
||||
match level.top() {
|
||||
true => content.total().map(|total| Extent::AtLeast(match total {
|
||||
true => content.total().map(|total| {
|
||||
Extent::AtLeast(match total {
|
||||
Extent::Exactly(count) | Extent::AtLeast(count) => count,
|
||||
Extent::Free => 0,
|
||||
})),
|
||||
})
|
||||
}),
|
||||
false => GPoll::Final(Extent::Exactly(1)),
|
||||
}
|
||||
}
|
||||
@@ -1644,10 +1590,12 @@ fn solidify_stroke_vector<'e>(
|
||||
|
||||
fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
|
||||
match level.top() {
|
||||
true => content.total().map(|total| Extent::AtLeast(match total {
|
||||
true => content.total().map(|total| {
|
||||
Extent::AtLeast(match total {
|
||||
Extent::Exactly(count) | Extent::AtLeast(count) => count,
|
||||
Extent::Free => 0,
|
||||
})),
|
||||
})
|
||||
}),
|
||||
false => GPoll::Final(Extent::Exactly(1)),
|
||||
}
|
||||
}
|
||||
@@ -1720,7 +1668,17 @@ fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Nod
|
||||
fn flatten_path_core<'e>(
|
||||
arena: &'e core_types::arena::Arena,
|
||||
graphic_list: List<Graphic>,
|
||||
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, Fill>, Attr<'e, StrokeAttr>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||
) -> Result<
|
||||
(
|
||||
Vector,
|
||||
Attr<'e, TransformAttr>,
|
||||
Attr<'e, Fill>,
|
||||
Attr<'e, StrokeAttr>,
|
||||
Attr<'e, EditorLayerPath>,
|
||||
Attr<'e, EditorMergedLayers>,
|
||||
),
|
||||
Interrupt,
|
||||
> {
|
||||
let flattened = graphic_list.clone().into_flattened_list::<Vector>();
|
||||
|
||||
let mut output = Vector::default();
|
||||
@@ -1787,7 +1745,17 @@ fn flatten_path_core<'e>(
|
||||
pub fn flatten_path<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
content: IList<Graphic>,
|
||||
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, Fill>, Attr<'e, StrokeAttr>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||
) -> Result<
|
||||
(
|
||||
Vector,
|
||||
Attr<'e, TransformAttr>,
|
||||
Attr<'e, Fill>,
|
||||
Attr<'e, StrokeAttr>,
|
||||
Attr<'e, EditorLayerPath>,
|
||||
Attr<'e, EditorMergedLayers>,
|
||||
),
|
||||
Interrupt,
|
||||
> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let content = graphic_types::graphic::run_to_render_list::<Graphic>(&item).expect("the run holds the row's element type");
|
||||
@@ -1800,7 +1768,17 @@ pub fn flatten_path<'e>(
|
||||
pub fn flatten_path_vector<'e>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
content: IList<Vector>,
|
||||
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, Fill>, Attr<'e, StrokeAttr>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||
) -> Result<
|
||||
(
|
||||
Vector,
|
||||
Attr<'e, TransformAttr>,
|
||||
Attr<'e, Fill>,
|
||||
Attr<'e, StrokeAttr>,
|
||||
Attr<'e, EditorLayerPath>,
|
||||
Attr<'e, EditorMergedLayers>,
|
||||
),
|
||||
Interrupt,
|
||||
> {
|
||||
// SAFETY: a materialized input's frames are arena-resident.
|
||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||
let content = graphic_types::graphic::run_to_render_list::<Vector>(&item)
|
||||
@@ -1896,31 +1874,28 @@ fn sample_polyline(
|
||||
fn simplify(
|
||||
_: impl Ctx,
|
||||
/// The vector paths to simplify.
|
||||
content: List<Vector>,
|
||||
(content, lane_transform): (Vector, Attr<TransformAttr>),
|
||||
/// The maximum distance the simplified path may deviate from the original.
|
||||
#[default(5.)]
|
||||
#[unit(" px")]
|
||||
tolerance: Length,
|
||||
) -> List<Vector> {
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
if tolerance <= 0. {
|
||||
return content;
|
||||
return (content, Attr(*lane_transform));
|
||||
}
|
||||
|
||||
let options = SimplifyOptions::default();
|
||||
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = *lane_transform;
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let inverse_transform = transform.inverse();
|
||||
|
||||
let mut result = Vector {
|
||||
stroke: std::mem::take(&mut row.element_mut().stroke),
|
||||
stroke: content.stroke.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for mut bezpath in row.element().stroke_bezpath_iter() {
|
||||
for mut bezpath in content.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(transform);
|
||||
|
||||
let mut simplified = simplify_bezpath(bezpath, tolerance, &options);
|
||||
@@ -1929,10 +1904,7 @@ fn simplify(
|
||||
result.append_bezpath(simplified);
|
||||
}
|
||||
|
||||
*row.element_mut() = result;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
(result, Attr(transform_attribute))
|
||||
}
|
||||
|
||||
/// Decimates vector paths into polylines by sampling any curves into line segments, then removing points that don't significantly contribute to the shape using the Ramer-Douglas-Peucker algorithm.
|
||||
@@ -1940,15 +1912,15 @@ fn simplify(
|
||||
fn decimate(
|
||||
_: impl Ctx,
|
||||
/// The vector paths to decimate.
|
||||
content: List<Vector>,
|
||||
(content, lane_transform): (Vector, Attr<TransformAttr>),
|
||||
/// The maximum distance a point can deviate from the simplified path before it is kept.
|
||||
#[default(5.)]
|
||||
#[unit(" px")]
|
||||
tolerance: Length,
|
||||
) -> List<Vector> {
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
// Tolerance of 0 means no simplification is possible, so return immediately
|
||||
if tolerance <= 0. {
|
||||
return content;
|
||||
return (content, Attr(*lane_transform));
|
||||
}
|
||||
|
||||
// Below this squared length, a line segment is treated as a degenerate point and the distance
|
||||
@@ -2004,19 +1976,16 @@ fn decimate(
|
||||
points.iter().enumerate().filter(|(i, _)| keep[*i]).map(|(_, p)| *p).collect()
|
||||
}
|
||||
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let transform_attribute: DAffine2 = *lane_transform;
|
||||
let transform = Affine::new(transform_attribute.to_cols_array());
|
||||
let inverse_transform = transform.inverse();
|
||||
|
||||
let mut result = Vector {
|
||||
stroke: std::mem::take(&mut row.element_mut().stroke),
|
||||
stroke: content.stroke.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for mut bezpath in row.element().stroke_bezpath_iter() {
|
||||
for mut bezpath in content.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(transform);
|
||||
|
||||
let is_closed = matches!(bezpath.elements().last(), Some(PathEl::ClosePath));
|
||||
@@ -2055,10 +2024,7 @@ fn decimate(
|
||||
result.append_bezpath(new_bezpath);
|
||||
}
|
||||
|
||||
*row.element_mut() = result;
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
(result, Attr(transform_attribute))
|
||||
}
|
||||
|
||||
/// Cuts a path at a given progression from 0 to 1 along the path, creating two new subpaths from the original one (if the path is initially open) or one open subpath (if the path is initially closed).
|
||||
@@ -2116,9 +2082,10 @@ fn cut_path(
|
||||
|
||||
/// Cuts path segments into separate disconnected pieces where each is a distinct subpath.
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
|
||||
// Iterate through every segment and make a copy of each of its endpoints, then reassign each segment's endpoints to its own unique point copy
|
||||
for vector in content.iter_element_values_mut() {
|
||||
fn cut_segments(_: impl Ctx, mut content: Vector) -> Vector {
|
||||
// Make a copy of each segment's endpoints, then reassign each segment's endpoints to its own unique point copy
|
||||
{
|
||||
let vector = &mut content;
|
||||
let points_count = vector.point_domain.ids().len();
|
||||
let segments_count = vector.segment_domain.ids().len();
|
||||
|
||||
@@ -2176,9 +2143,9 @@ fn cut_segments(_: impl Ctx, mut content: List<Vector>) -> List<Vector> {
|
||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||
#[node_macro::node(name("Position on Path"), category("Vector: Measure"), path(graphene_core::vector))]
|
||||
fn position_on_path(
|
||||
_: impl Ctx,
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The path to traverse.
|
||||
content: List<Vector>,
|
||||
content: IList<Vector>,
|
||||
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
||||
progression: Progression,
|
||||
/// Swap the direction of the path.
|
||||
@@ -2190,8 +2157,8 @@ fn position_on_path(
|
||||
|
||||
let mut bezpaths: Vec<_> = (0..content.len())
|
||||
.flat_map(|index| {
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
|
||||
let transform: DAffine2 = content.lane(index).attr::<TransformAttr>();
|
||||
content.element_ref(index).stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
let bezpath_count = bezpaths.len() as f64;
|
||||
@@ -2214,9 +2181,9 @@ fn position_on_path(
|
||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||
#[node_macro::node(name("Tangent on Path"), category("Vector: Measure"), path(graphene_core::vector))]
|
||||
fn tangent_on_path(
|
||||
_: impl Ctx,
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The path to traverse.
|
||||
content: List<Vector>,
|
||||
content: IList<Vector>,
|
||||
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
||||
progression: Progression,
|
||||
/// Swap the direction of the path.
|
||||
@@ -2230,8 +2197,8 @@ fn tangent_on_path(
|
||||
|
||||
let mut bezpaths: Vec<_> = (0..content.len())
|
||||
.flat_map(|index| {
|
||||
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
|
||||
let transform: DAffine2 = content.lane(index).attr::<TransformAttr>();
|
||||
content.element_ref(index).stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
let bezpath_count = bezpaths.len() as f64;
|
||||
@@ -2447,21 +2414,17 @@ fn jitter_points(
|
||||
fn offset_points(
|
||||
_: impl Ctx,
|
||||
/// The vector geometry with points to be offset.
|
||||
content: List<Vector>,
|
||||
(mut element, transform): (Vector, Attr<TransformAttr>),
|
||||
/// The distance to offset each anchor point along its normal. Positive values move outward, negative values move inward.
|
||||
#[default(10.)]
|
||||
#[unit(" px")]
|
||||
distance: f64,
|
||||
) -> List<Vector> {
|
||||
content
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2);
|
||||
) -> (Vector, Attr<TransformAttr>) {
|
||||
let inverse_linear = inverse_linear_or_repair(transform.matrix2);
|
||||
|
||||
let deltas: Vec<_> = (0..row.element().point_domain.positions().len())
|
||||
let deltas: Vec<_> = (0..element.point_domain.positions().len())
|
||||
.map(|point_index| {
|
||||
let Some(normal) = row.element().segment_domain.point_tangent(point_index, row.element().point_domain.positions()).map(|t| -t.perp()) else {
|
||||
let Some(normal) = element.segment_domain.point_tangent(point_index, element.point_domain.positions()).map(|t| -t.perp()) else {
|
||||
return DVec2::ZERO;
|
||||
};
|
||||
|
||||
@@ -2469,12 +2432,9 @@ fn offset_points(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
apply_point_deltas(row.element_mut(), &deltas, transform);
|
||||
apply_point_deltas(&mut element, &deltas, *transform);
|
||||
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
(element, Attr(*transform))
|
||||
}
|
||||
|
||||
/// Interpolates the geometry, appearance, and transform between multiple vector layers, producing a single morphed vector shape.
|
||||
@@ -3481,34 +3441,21 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn bevel(_: impl Ctx, source: List<Vector>, #[default(10.)] distance: Length) -> List<Vector> {
|
||||
source
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let (element, attributes) = row.into_parts();
|
||||
|
||||
Item::from_parts(bevel_algorithm(element, transform, distance), attributes)
|
||||
})
|
||||
.collect()
|
||||
fn bevel(_: impl Ctx, (element, transform): (Vector, Attr<TransformAttr>), #[default(10.)] distance: Length) -> (Vector, Attr<TransformAttr>) {
|
||||
(bevel_algorithm(element, *transform, distance), Attr(*transform))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||
fn close_path(_: impl Ctx, source: List<Vector>) -> List<Vector> {
|
||||
fn close_path(_: impl Ctx, mut source: Vector) -> Vector {
|
||||
source.close_subpaths();
|
||||
source
|
||||
.into_iter()
|
||||
.map(|mut row| {
|
||||
row.element_mut().close_subpaths();
|
||||
row
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn point_inside(_: impl Ctx, source: List<Vector>, point: DVec2) -> bool {
|
||||
source.into_iter().any(|row| {
|
||||
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
row.element().check_point_inside_shape(transform, point)
|
||||
fn point_inside(_: impl Ctx + ExtractIndex + InjectIndex + Copy, source: IList<Vector>, point: DVec2) -> bool {
|
||||
(0..source.len()).any(|index| {
|
||||
let transform: DAffine2 = source.lane(index).attr::<TransformAttr>();
|
||||
source.element_ref(index).check_point_inside_shape(transform, point)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3520,21 +3467,21 @@ fn count_elements(_: impl Ctx, content: ListDyn) -> f64 {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
fn count_points(_: impl Ctx, content: List<Vector>) -> f64 {
|
||||
content.iter_element_values().map(|vector| vector.point_domain.positions().len() as f64).sum()
|
||||
fn count_points(_: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<Vector>) -> f64 {
|
||||
(0..content.len()).map(|index| content.element_ref(index).point_domain.positions().len() as f64).sum()
|
||||
}
|
||||
|
||||
/// Retrieves the vec2 position (in local space) of the anchor point at the specified index in a `List` of vector elements.
|
||||
/// If no value exists at that index, the position (0, 0) is returned.
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
fn index_points(
|
||||
_: impl Ctx,
|
||||
_: impl Ctx + ExtractIndex + InjectIndex + Copy,
|
||||
/// The vector element or elements containing the anchor points to be retrieved.
|
||||
content: List<Vector>,
|
||||
content: IList<Vector>,
|
||||
/// The index of the points to retrieve, starting from 0 for the first point. Negative indices count backwards from the end, starting from -1 for the last item.
|
||||
index: f64,
|
||||
) -> DVec2 {
|
||||
let points_count = content.iter_element_values().map(|vector| vector.point_domain.positions().len()).sum::<usize>();
|
||||
let points_count = (0..content.len()).map(|row| content.element_ref(row).point_domain.positions().len()).sum::<usize>();
|
||||
|
||||
if points_count == 0 {
|
||||
return DVec2::ZERO;
|
||||
@@ -3549,7 +3496,8 @@ fn index_points(
|
||||
|
||||
// Find the point at the given index across all vector elements
|
||||
let mut accumulated = 0;
|
||||
for vector in content.iter_element_values() {
|
||||
for row in 0..content.len() {
|
||||
let vector = content.element_ref(row);
|
||||
let row_point_count = vector.point_domain.positions().len();
|
||||
if index - accumulated < row_point_count {
|
||||
return vector.point_domain.positions()[index - accumulated];
|
||||
@@ -3561,14 +3509,13 @@ fn index_points(
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
|
||||
fn path_length(_: impl Ctx + ExtractIndex + InjectIndex + Copy, source: IList<Vector>) -> f64 {
|
||||
(0..source.len())
|
||||
.map(|index| {
|
||||
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform: DAffine2 = source.lane(index).attr::<TransformAttr>();
|
||||
|
||||
source
|
||||
.element(index)
|
||||
.unwrap()
|
||||
.element_ref(index)
|
||||
.stroke_bezpath_iter()
|
||||
.map(|mut bezpath| {
|
||||
bezpath.apply_affine(Affine::new(transform.to_cols_array()));
|
||||
@@ -3580,22 +3527,21 @@ fn path_length(_: impl Ctx, source: List<Vector>) -> f64 {
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn area(ctx: impl Ctx + DeriveCtx, content: impl Node<Context<'_>, Output = List<Vector>>) -> Result<f64, Interrupt> {
|
||||
let vector = content.eval(&ctx.with_footprint(&Footprint::DEFAULT))?;
|
||||
|
||||
// The legacy form reset the footprint before evaluating; the nullification
|
||||
// pass now strips it upstream since this node declares no footprint feature.
|
||||
fn area(_: impl Ctx + ExtractIndex + InjectIndex + Copy, vector: IList<Vector>) -> Result<f64, Interrupt> {
|
||||
Ok((0..vector.len())
|
||||
.map(|index| {
|
||||
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform: DAffine2 = vector.lane(index).attr::<TransformAttr>();
|
||||
let area_scale = transform.matrix2.determinant().abs();
|
||||
vector.element(index).unwrap().stroke_bezpath_iter().map(|subpath| subpath.area() * area_scale).sum::<f64>()
|
||||
vector.element_ref(index).stroke_bezpath_iter().map(|subpath| subpath.area() * area_scale).sum::<f64>()
|
||||
})
|
||||
.sum())
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
|
||||
fn centroid(ctx: impl Ctx + DeriveCtx, content: impl Node<Context<'_>, Output = List<Vector>>, centroid_type: CentroidType) -> Result<DVec2, Interrupt> {
|
||||
let vector = content.eval(&ctx.with_footprint(&Footprint::DEFAULT))?;
|
||||
|
||||
// The footprint reset moved to the nullification pass, as in `area`.
|
||||
fn centroid(_: impl Ctx + ExtractIndex + InjectIndex + Copy, vector: IList<Vector>, centroid_type: CentroidType) -> Result<DVec2, Interrupt> {
|
||||
if vector.is_empty() {
|
||||
return Ok(DVec2::ZERO);
|
||||
}
|
||||
@@ -3606,14 +3552,14 @@ fn centroid(ctx: impl Ctx + DeriveCtx, content: impl Node<Context<'_>, Output =
|
||||
let mut sum = 0.;
|
||||
|
||||
for index in 0..vector.len() {
|
||||
let Some(element) = vector.element(index) else { continue };
|
||||
let element = vector.element_ref(index);
|
||||
for subpath in element.stroke_bezier_paths() {
|
||||
let partial = match centroid_type {
|
||||
CentroidType::Area => subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.),
|
||||
CentroidType::Length => subpath.length_centroid_and_length(None, true),
|
||||
};
|
||||
if let Some((subpath_centroid, area_or_length)) = partial {
|
||||
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform: DAffine2 = vector.lane(index).attr::<TransformAttr>();
|
||||
let subpath_centroid = transform.transform_point2(subpath_centroid);
|
||||
|
||||
sum += area_or_length;
|
||||
@@ -3631,10 +3577,9 @@ fn centroid(ctx: impl Ctx + DeriveCtx, content: impl Node<Context<'_>, Output =
|
||||
|
||||
let summed_positions = (0..vector.len())
|
||||
.flat_map(|index| {
|
||||
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform: DAffine2 = vector.lane(index).attr::<TransformAttr>();
|
||||
vector
|
||||
.element(index)
|
||||
.unwrap()
|
||||
.element_ref(index)
|
||||
.point_domain
|
||||
.positions()
|
||||
.iter()
|
||||
@@ -3651,6 +3596,7 @@ fn centroid(ctx: impl Ctx + DeriveCtx, content: impl Node<Context<'_>, Output =
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core_types::transform::Footprint;
|
||||
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
||||
use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};
|
||||
use vector_types::vector::misc::pathseg_abs_diff_eq;
|
||||
@@ -3659,16 +3605,9 @@ mod test {
|
||||
List::new_from_element(Vector::from_bezpath(bezpath))
|
||||
}
|
||||
|
||||
fn create_vector_item(bezpath: BezPath, transform: DAffine2) -> Item<Vector> {
|
||||
let mut row = Vector::default();
|
||||
row.append_bezpath(bezpath);
|
||||
Item::new_from_element(row).with_attribute(ATTR_TRANSFORM, transform)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box() {
|
||||
let bounding_box = super::bounding_box(&(), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)));
|
||||
let bounding_box = bounding_box.element(0).unwrap();
|
||||
let bounding_box = super::bounding_box(&(), Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)));
|
||||
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
||||
let manipulator_groups_anchors = bounding_box
|
||||
.region_manipulator_groups()
|
||||
@@ -3681,12 +3620,9 @@ mod test {
|
||||
|
||||
assert_eq!(&manipulator_groups_anchors[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
|
||||
|
||||
// Test a rectangular path with non-zero rotation
|
||||
// The box spans local space, so a lane rotation leaves it unchanged
|
||||
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
|
||||
let mut square = List::new_from_element(square);
|
||||
square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
|
||||
let bounding_box = super::bounding_box(&(), square);
|
||||
let bounding_box = bounding_box.element(0).unwrap();
|
||||
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
|
||||
let manipulator_groups_anchors = bounding_box
|
||||
.region_manipulator_groups()
|
||||
@@ -3705,7 +3641,16 @@ mod test {
|
||||
#[test]
|
||||
fn sample_polyline() {
|
||||
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
||||
let (sample_polyline, _) = super::sample_polyline(&Footprint::default(), (Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)), PointSpacingType::Separation, 30., 0, 0., 0., false);
|
||||
let (sample_polyline, _) = super::sample_polyline(
|
||||
&Footprint::default(),
|
||||
(Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)),
|
||||
PointSpacingType::Separation,
|
||||
30.,
|
||||
0,
|
||||
0.,
|
||||
0.,
|
||||
false,
|
||||
);
|
||||
let sample_polyline = &sample_polyline;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
@@ -3715,7 +3660,16 @@ mod test {
|
||||
#[test]
|
||||
fn sample_polyline_adaptive_spacing() {
|
||||
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);
|
||||
let (sample_polyline, _) = super::sample_polyline(&Footprint::default(), (Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)), PointSpacingType::Separation, 18., 0, 45., 10., true);
|
||||
let (sample_polyline, _) = super::sample_polyline(
|
||||
&Footprint::default(),
|
||||
(Vector::from_bezpath(path), Attr(DAffine2::IDENTITY)),
|
||||
PointSpacingType::Separation,
|
||||
18.,
|
||||
0,
|
||||
45.,
|
||||
10.,
|
||||
true,
|
||||
);
|
||||
let sample_polyline = &sample_polyline;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
@@ -3742,15 +3696,27 @@ mod test {
|
||||
}
|
||||
#[test]
|
||||
fn path_length() {
|
||||
core_types::record::stack::reserve(1 << 16);
|
||||
let arena = core_types::arena::Arena::new(1 << 20).unwrap();
|
||||
let generations = [];
|
||||
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = core_types::context::ContextImpl::root(&scope);
|
||||
|
||||
let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY);
|
||||
let transform = DAffine2::from_scale(DVec2::new(2., 2.));
|
||||
let row = create_vector_item(bezpath, transform);
|
||||
let list = (0..5).map(|_| row.clone()).collect::<List<Vector>>();
|
||||
let mut row = Vector::default();
|
||||
row.append_bezpath(bezpath);
|
||||
// Element-only lanes read identity lane transforms; the transform term
|
||||
// rides the demo gate.
|
||||
let source = core_types::value::LeveledValueSource::new(vec![row; 5]);
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else {
|
||||
panic!("materialize failed")
|
||||
};
|
||||
let list = unsafe { core_types::node::List::<Vector>::new(batch) };
|
||||
|
||||
let length = super::path_length(&Footprint::default(), list);
|
||||
let length = super::path_length(&ctx, list);
|
||||
|
||||
// 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows)
|
||||
assert_eq!(length, 101. * 4. * 2. * 5.);
|
||||
// 101 (each rectangle edge length) * 4 (rectangle perimeter) * 5 (number of rows)
|
||||
assert_eq!(length, 101. * 4. * 5.);
|
||||
}
|
||||
#[test]
|
||||
fn spline() {
|
||||
@@ -3825,8 +3791,8 @@ mod test {
|
||||
#[test]
|
||||
fn bevel_rect() {
|
||||
let source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY);
|
||||
let beveled = super::bevel(&Footprint::default(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.);
|
||||
let beveled = beveled.element(0).unwrap();
|
||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
||||
let beveled = &beveled;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||
@@ -3853,8 +3819,8 @@ mod test {
|
||||
source.line_to(Point::ZERO);
|
||||
source.push(curve.as_path_el());
|
||||
|
||||
let beveled = super::bevel(&(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.);
|
||||
let beveled = beveled.element(0).unwrap();
|
||||
let (beveled, _) = super::bevel(&(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
||||
let beveled = &beveled;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
@@ -3877,13 +3843,11 @@ mod test {
|
||||
source.line_to(Point::ZERO);
|
||||
source.push(curve.as_path_el());
|
||||
|
||||
// The legacy test set the transform on a list it never passed, so the
|
||||
// evaluated lane used the identity; keep that behavior explicit.
|
||||
let vector = Vector::from_bezpath(source);
|
||||
let mut vector_list = List::new_from_element(vector.clone());
|
||||
|
||||
vector_list.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)));
|
||||
|
||||
let beveled = super::bevel(&(), List::new_from_element(vector), 2_f64.sqrt() * 10.);
|
||||
let beveled = beveled.element(0).unwrap();
|
||||
let (beveled, _) = super::bevel(&(), (vector, Attr(DAffine2::IDENTITY)), 2_f64.sqrt() * 10.);
|
||||
let beveled = &beveled;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
@@ -3905,8 +3869,8 @@ mod test {
|
||||
source.line_to(Point::new(100., 100.));
|
||||
source.line_to(Point::new(0., 100.));
|
||||
|
||||
let beveled = super::bevel(&Footprint::default(), vector_node_from_bezpath(source), 999.);
|
||||
let beveled = beveled.element(0).unwrap();
|
||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(source), Attr(DAffine2::IDENTITY)), 999.);
|
||||
let beveled = &beveled;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
@@ -3929,8 +3893,8 @@ mod test {
|
||||
|
||||
let subpath = BezPath::from_path_segments([line, point, curve].into_iter());
|
||||
|
||||
let beveled_list = super::bevel(&Footprint::default(), vector_node_from_bezpath(subpath), 5.);
|
||||
let beveled = beveled_list.element(0).unwrap();
|
||||
let (beveled, _) = super::bevel(&Footprint::default(), (Vector::from_bezpath(subpath), Attr(DAffine2::IDENTITY)), 5.);
|
||||
let beveled = &beveled;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
Reference in New Issue
Block a user