Add nondestructive vector editing (#1676)

* Initial vector modify node

* Initial extraction of data from monitor nodes

* Migrate to point id

* Start converting to modify node

* Non destructive spline tool (tout le reste est cassé)

* Fix unconnected modify node

* Fix freehand tool

* Pen tool

* Migrate demo art

* Select points

* Fix the demo artwork

* Fix the X and Y inputs for path tool

* G1 continous toggle

* Delete points

* Fix test

* Insert point

* Improve robustness of handles

* Fix GRS shortcuts on path

* Dragging points

* Fix build

* Preserve opposing handle lengths

* Update demo art and snapping

* Fix polygon tool

* Double click end anchor

* Improve dragging

* Fix text shifting

* Select only connected verts

* Colinear alt

* Cleanup

* Fix imports

* Improve pen tool avoiding handle placement

* Improve disolve

* Remove pivot widget from Transform node properties

* Fix demo art

* Fix bugs

* Re-save demo artwork

* Code review

* Serialize hashmap as tuple vec to enable deserialize_inputs

* Fix migrate

* Add document upgrade function to editor_api.rs

* Finalize document upgrading

* Rename to the Path node

* Remove smoothing from Freehand tool

* Upgrade demo artwork

* Propertly disable raw-rs tests

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Adam <adamgerhant@gmail.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
James Lindsay 2024-07-05 21:42:40 +01:00 committed by GitHub
parent fd3613018a
commit 1652c713a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 3343 additions and 2622 deletions

View file

@ -65,3 +65,7 @@ rand = { workspace = true, default-features = false, features = ["std_rng"] }
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
[lints.rust]
# the spirv target is not in the list of common cfgs so must be added manually
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_arch, values("spirv"))'] }

View file

@ -334,8 +334,14 @@ impl GraphicElementRendered for VectorData {
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
click_targets.extend(self.region_bezier_paths().map(|(_, subpath)| ClickTarget { stroke_width, subpath }));
click_targets.extend(self.stroke_bezier_paths().map(|subpath| ClickTarget { stroke_width, subpath }));
let filled = self.style.fill() != &crate::vector::style::Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
}
subpath
};
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
}
fn to_usvg_node(&self) -> usvg::Node {
@ -475,7 +481,7 @@ impl GraphicElementRendered for crate::ArtboardGroup {
}
fn contains_artboard(&self) -> bool {
self.artboards.len() > 0
!self.artboards.is_empty()
}
}

View file

@ -581,8 +581,9 @@ fn vibrance_node(color: Color, vibrance: f64) -> Color {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RedGreenBlue {
#[default]
Red,
Green,
Blue,
@ -600,8 +601,9 @@ impl core::fmt::Display for RedGreenBlue {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RedGreenBlueAlpha {
#[default]
Red,
Green,
Blue,
@ -621,8 +623,9 @@ impl core::fmt::Display for RedGreenBlueAlpha {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum NoiseType {
#[default]
Perlin,
OpenSimplex2,
OpenSimplex2S,
@ -662,8 +665,9 @@ impl NoiseType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum FractalType {
#[default]
None,
FBm,
Ridged,
@ -700,8 +704,9 @@ impl FractalType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum CellularDistanceFunction {
#[default]
Euclidean,
EuclideanSq,
Manhattan,
@ -732,9 +737,10 @@ impl CellularDistanceFunction {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum CellularReturnType {
CellValue,
#[default]
Nearest,
NextNearest,
Average,
@ -773,8 +779,9 @@ impl CellularReturnType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum DomainWarpType {
#[default]
None,
OpenSimplex2,
OpenSimplex2Reduced,
@ -867,8 +874,9 @@ fn channel_mixer_node(
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RelativeAbsolute {
#[default]
Relative,
Absolute,
}
@ -885,8 +893,9 @@ impl core::fmt::Display for RelativeAbsolute {
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum SelectiveColorChoice {
#[default]
Reds,
Yellows,
Greens,

View file

@ -774,7 +774,7 @@ impl Color {
/// ```
#[inline(always)]
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
let gamma = self.to_gamma_srgb();
let gamma = self.to_gamma_srgb().to_gamma_srgb();
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
}

View file

@ -17,5 +17,5 @@ pub struct TextGeneratorNode<Text, FontName, Size> {
#[node_fn(TextGeneratorNode)]
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
}

View file

@ -15,7 +15,11 @@ impl Font {
Self { font_family, font_style }
}
}
impl Default for Font {
fn default() -> Self {
Self::new(crate::consts::DEFAULT_FONT_FAMILY.into(), crate::consts::DEFAULT_FONT_STYLE.into())
}
}
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)]
pub struct FontCache {

View file

@ -1,4 +1,4 @@
use crate::uuid::ManipulatorGroupId;
use crate::vector::PointId;
use bezier_rs::{ManipulatorGroup, Subpath};
@ -7,13 +7,13 @@ use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
struct Builder {
current_subpath: Subpath<ManipulatorGroupId>,
other_subpaths: Vec<Subpath<ManipulatorGroupId>>,
current_subpath: Subpath<PointId>,
other_subpaths: Vec<Subpath<PointId>>,
pos: DVec2,
offset: DVec2,
ascender: f64,
scale: f64,
id: ManipulatorGroupId,
id: PointId,
}
impl Builder {
@ -37,7 +37,7 @@ impl OutlineBuilder for Builder {
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(anchor, self.id.next_id()));
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, None, None, self.id.next_id()));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
@ -80,7 +80,7 @@ fn wrap_word(line_width: Option<f64>, glyph_buffer: &GlyphBuffer, scale: f64, x_
false
}
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<ManipulatorGroupId>> {
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<PointId>> {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
@ -96,7 +96,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, li
offset: DVec2::ZERO,
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
scale,
id: ManipulatorGroupId::ZERO,
id: PointId::ZERO,
};
for line in str.split('\n') {

View file

@ -152,7 +152,7 @@ pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, P
pub(crate) rotate: Rotation,
pub(crate) scale: Scale,
pub(crate) shear: Shear,
pub(crate) pivot: Pivot,
pub(crate) _pivot: Pivot,
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
@ -165,7 +165,7 @@ pub enum RenderQuality {
Scale(f32),
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
/// This should be used to gradually update the render quality of a cached node
Probabilty(f32),
Probability(f32),
/// Render at full quality
Full,
}
@ -249,23 +249,18 @@ pub(crate) async fn transform_vector_data<Fut: Future>(
rotate: f64,
scale: DVec2,
shear: DVec2,
pivot: DVec2,
_pivot: DVec2,
) -> Fut::Output
where
Fut::Output: TransformMut,
{
// TODO: This is hack and might break for Vector data because the pivot may be incorrect
let transform = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
if !footprint.ignore_modifications {
let pivot_transform = DAffine2::from_translation(pivot);
let modification = pivot_transform * transform * pivot_transform.inverse();
*footprint.transform_mut() = footprint.transform() * modification;
}
let mut data = self.transform_target.eval(footprint).await;
let pivot_transform = DAffine2::from_translation(data.local_pivot(pivot));
let modification = pivot_transform * transform * pivot_transform.inverse();
let data_transform = data.transform_mut();
*data_transform = modification * (*data_transform);

View file

@ -1,5 +1,3 @@
use dyn_any::{DynAny, StaticType};
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Uuid(
#[serde(with = "u64_string")]
@ -69,33 +67,3 @@ mod uuid_generation {
}
pub use uuid_generation::*;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ManipulatorGroupId(u64);
impl bezier_rs::Identifier for ManipulatorGroupId {
fn new() -> Self {
Self(generate_uuid())
}
}
impl ManipulatorGroupId {
pub const ZERO: ManipulatorGroupId = ManipulatorGroupId(0);
pub fn next_id(&mut self) -> Self {
let old = self.0;
self.0 += 1;
Self(old)
}
pub(crate) fn inner(self) -> u64 {
self.0
}
}
impl From<crate::vector::PointId> for ManipulatorGroupId {
fn from(value: crate::vector::PointId) -> Self {
Self(value.inner())
}
}

View file

@ -1,5 +1,5 @@
use crate::uuid::ManipulatorGroupId;
use crate::vector::VectorData;
use super::HandleId;
use crate::vector::{PointId, VectorData};
use crate::Node;
use bezier_rs::Subpath;
@ -28,7 +28,14 @@ fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2))
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[derive(Debug, Clone, Copy)]
@ -46,7 +53,7 @@ trait CornerRadius {
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])])
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))
}
}
impl CornerRadius for [f64; 4] {
@ -66,7 +73,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)])
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))
}
}
@ -122,7 +129,11 @@ pub struct SplineGenerator<Positions> {
#[node_macro::node_fn(SplineGenerator)]
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
super::VectorData::from_subpath(Subpath::new_cubic_spline(positions))
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
}
spline
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
@ -132,9 +143,12 @@ pub struct PathGenerator<ColinearManipulators> {
}
#[node_macro::node_fn(PathGenerator)]
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, colinear_manipulators: Vec<ManipulatorGroupId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data);
vector_data.colinear_manipulators = colinear_manipulators;
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
vector_data
}

View file

@ -371,16 +371,18 @@ impl From<Fill> for FillChoice {
/// Enum describing the type of [Fill].
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
pub enum FillType {
#[default]
Solid,
Gradient,
}
/// The stroke (outline) style of an SVG element.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
pub enum LineCap {
#[default]
Butt,
Round,
Square,
@ -397,8 +399,9 @@ impl Display for LineCap {
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
pub enum LineJoin {
#[default]
Miter,
Bevel,
Round,

View file

@ -1,13 +1,15 @@
mod attributes;
mod modification;
pub use attributes::*;
pub use modification::*;
use super::style::{PathStyle, Stroke};
use crate::Color;
use crate::{uuid::ManipulatorGroupId, AlphaBlending};
pub use attributes::*;
use crate::{AlphaBlending, Color};
use bezier_rs::ManipulatorGroup;
use dyn_any::{DynAny, StaticType};
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};
/// [VectorData] is passed between nodes.
@ -20,7 +22,7 @@ pub struct VectorData {
pub alpha_blending: AlphaBlending,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<ManipulatorGroupId>,
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
@ -54,15 +56,15 @@ impl VectorData {
}
/// Construct some new vector data from a single subpath with an identity transform and black fill.
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
Self::from_subpaths([subpath])
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
Self::from_subpaths([subpath], false)
}
/// Push a subpath to the vector data
pub fn append_subpath<Id: bezier_rs::Identifier + Into<PointId> + Copy>(&mut self, subpath: bezier_rs::Subpath<Id>) {
for point in subpath.manipulator_groups() {
self.point_domain.push(point.id.into(), point.anchor);
}
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
let stroke_id = StrokeId::ZERO;
let mut point_id = self.point_domain.next_id();
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
(None, None) => bezier_rs::BezierHandles::Linear,
@ -70,33 +72,57 @@ impl VectorData {
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
};
let [mut first_seg, mut last_seg] = [None, None];
let mut segment_id = self.segment_domain.next_id();
let mut last_point = None;
let mut first_point = None;
for pair in subpath.manipulator_groups().windows(2) {
let id = SegmentId::generate();
let start = last_point.unwrap_or_else(|| {
let id = if preserve_id && !self.point_domain.ids().contains(&pair[0].id) {
pair[0].id
} else {
point_id.next_id()
};
self.point_domain.push(id, pair[0].anchor);
id
});
first_point = Some(first_point.unwrap_or(start));
let end = if preserve_id && !self.point_domain.ids().contains(&pair[1].id) {
pair[1].id
} else {
point_id.next_id()
};
self.point_domain.push(end, pair[1].anchor);
let id = segment_id.next_id();
first_seg = Some(first_seg.unwrap_or(id));
last_seg = Some(id);
self.segment_domain.push(id, pair[0].id.into(), pair[1].id.into(), handles(&pair[0], &pair[1]), StrokeId::generate());
self.segment_domain.push(id, start, end, handles(&pair[0], &pair[1]), stroke_id);
last_point = Some(end);
}
let fill_id = FillId::ZERO;
if subpath.closed() {
if let (Some(last), Some(first)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first()) {
let id = SegmentId::generate();
if let (Some(last), Some(first), Some(first_id), Some(last_id)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first(), first_point, last_point) {
let id = segment_id.next_id();
first_seg = Some(first_seg.unwrap_or(id));
last_seg = Some(id);
self.segment_domain.push(id, last.id.into(), first.id.into(), handles(last, first), StrokeId::generate());
self.segment_domain.push(id, last_id, first_id, handles(last, first), stroke_id);
}
if let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] {
self.region_domain.push(RegionId::generate(), first_seg..=last_seg, FillId::generate());
self.region_domain.push(self.region_domain.next_id(), first_seg..=last_seg, fill_id);
}
}
}
/// Construct some new vector data from subpaths with an identity transform and black fill.
pub fn from_subpaths(subpaths: impl IntoIterator<Item = bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
let mut vector_data = Self::empty();
for subpath in subpaths.into_iter() {
vector_data.append_subpath(subpath);
vector_data.append_subpath(subpath, preserve_id);
}
vector_data
@ -142,6 +168,33 @@ impl VectorData {
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
}
/// Points connected to a single segment
pub fn single_connected_points(&self) -> impl Iterator<Item = PointId> + '_ {
self.point_domain.ids().iter().copied().filter(|&point| self.segment_domain.connected_count(point) == 1)
}
/// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle.
pub fn colinear(&self, point: ManipulatorPointId) -> bool {
let has_handle = |target| self.colinear_manipulators.iter().flatten().any(|&handle| handle == target);
match point {
ManipulatorPointId::Anchor(id) => {
self.segment_domain.start_connected(id).all(|segment| has_handle(HandleId::primary(segment))) && self.segment_domain.end_connected(id).all(|segment| has_handle(HandleId::end(segment)))
}
ManipulatorPointId::PrimaryHandle(segment) => has_handle(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => has_handle(HandleId::end(segment)),
}
}
pub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId> {
let pair = self.colinear_manipulators.iter().find(|pair| pair.iter().any(|&val| val == handle))?;
let other = pair.iter().copied().find(|&val| val != handle)?;
if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {
Some(other)
} else {
None
}
}
}
impl Default for VectorData {
@ -150,64 +203,192 @@ impl Default for VectorData {
}
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curviture).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ManipulatorPointId {
pub group: ManipulatorGroupId,
pub manipulator_type: SelectedType,
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
impl ManipulatorPointId {
pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self {
Self { group, manipulator_type }
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
pub fn get_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector_data.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first to handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector_data.segment_domain.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector_data: &VectorData) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector_data.segment_domain.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector_data.segment_domain.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SelectedType {
Anchor = 1 << 0,
InHandle = 1 << 1,
OutHandle = 1 << 2,
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
impl SelectedType {
/// Get the location of the [SelectedType] in the [ManipulatorGroup]
pub fn get_position(&self, manipulator_group: &ManipulatorGroup<ManipulatorGroupId>) -> Option<DVec2> {
match self {
Self::Anchor => Some(manipulator_group.anchor),
Self::InHandle => manipulator_group.in_handle,
Self::OutHandle => manipulator_group.out_handle,
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Get the closest [SelectedType] in the [ManipulatorGroup].
pub fn closest_widget(manipulator_group: &ManipulatorGroup<ManipulatorGroupId>, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) {
let anchor = transform_space.transform_point2(manipulator_group.anchor);
// Skip handles under the anchor
let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2);
let compute_distance = |selected_type: Self| {
selected_type.get_position(manipulator_group).and_then(|position| {
Some((selected_type, transform_space.transform_point2(position)))
.filter(not_under_anchor)
.map(|(selected_type, pos)| (selected_type, pos.distance_squared(target)))
})
};
[Self::Anchor, Self::InHandle, Self::OutHandle]
.into_iter()
.filter_map(compute_distance)
.min_by(|a, b| a.1.total_cmp(&b.1))
.unwrap_or((Self::Anchor, manipulator_group.anchor.distance_squared(target)))
}
/// Opposite handle
pub fn opposite(&self) -> Self {
match self {
SelectedType::Anchor => SelectedType::Anchor,
SelectedType::InHandle => SelectedType::OutHandle,
SelectedType::OutHandle => SelectedType::InHandle,
/// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle.
#[must_use]
pub fn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {
let Self { ty, segment } = self;
match ty {
HandleType::Primary => VectorModificationType::SetPrimaryHandle { segment, relative_position },
HandleType::End => VectorModificationType::SetEndHandle { segment, relative_position },
}
}
/// Check if handle
pub fn is_handle(self) -> bool {
self != SelectedType::Anchor
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}
#[cfg(test)]
fn assert_subpath_eq(generated: &Vec<bezier_rs::Subpath<PointId>>, expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
}
#[test]
fn construct_closed_subpath() {
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpath(&circle);
assert_eq!(vector_data.point_domain.ids().len(), 4);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths.len(), 4);
assert!(bézier_paths.iter().all(|&bézier| circle.iter().any(|original_bézier| original_bézier == bézier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[circle]);
}
#[test]
fn construct_open_subpath() {
let bézier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let subpath = bezier_rs::Subpath::from_bezier(&bézier);
let vector_data = VectorData::from_subpath(&subpath);
assert_eq!(vector_data.point_domain.ids().len(), 2);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths, vec![bézier]);
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[subpath]);
}
#[test]
fn construct_many_subpath() {
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let curve = bezier_rs::Subpath::from_bezier(&curve);
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
assert_eq!(vector_data.point_domain.ids().len(), 6);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths.len(), 5);
assert!(bézier_paths.iter().all(|&bézier| circle.iter().chain(curve.iter()).any(|original_bézier| original_bézier == bézier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[curve, circle]);
}

View file

@ -1,8 +1,11 @@
use super::HandleId;
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
use std::collections::HashMap;
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
macro_rules! create_ids {
($($id:ident),*) => {
$(
@ -12,14 +15,23 @@ macro_rules! create_ids {
pub struct $id(u64);
impl $id {
pub const ZERO: $id = $id(0);
/// Generate a new random id
pub fn generate() -> Self {
Self(crate::uuid::generate_uuid())
}
/// Gets the inner raw value.
pub fn inner(self) -> u64 {
self.0
}
/// Adds one to the current value and returns the old value. Note that the ids are not going to be unique unless you use the largest id.
pub fn next_id(&mut self) -> Self {
self.0 += 1;
*self
}
}
)*
};
@ -55,7 +67,17 @@ impl PointDomain {
self.positions.clear();
}
pub fn retain(&mut self, f: impl Fn(&PointId) -> bool) {
let mut keep = self.id.iter().map(&f);
self.positions.retain(|_| keep.next().unwrap_or_default());
self.id.retain(f);
}
pub fn push(&mut self, id: PointId, position: DVec2) {
if self.id.contains(&id) {
warn!("Duplicate point");
return;
}
self.id.push(id);
self.positions.push(position);
}
@ -64,11 +86,19 @@ impl PointDomain {
&self.positions
}
pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {
self.id.iter().copied().zip(self.positions.iter_mut())
}
pub fn ids(&self) -> &[PointId] {
&self.id
}
pub fn pos_from_id(&self, id: PointId) -> Option<DVec2> {
pub fn next_id(&self) -> PointId {
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(PointId::ZERO)
}
pub fn position_from_id(&self, id: PointId) -> Option<DVec2> {
let pos = self.resolve_id(id).map(|index| self.positions[index]);
if pos.is_none() {
warn!("Resolving pos of invalid id");
@ -99,7 +129,6 @@ pub struct SegmentDomain {
ids: Vec<SegmentId>,
start_point: Vec<PointId>,
end_point: Vec<PointId>,
// TODO: Also store handle points as `PointId`s rather than Bezier-rs's internal `DVec2`s
handles: Vec<bezier_rs::BezierHandles>,
stroke: Vec<StrokeId>,
}
@ -123,21 +152,116 @@ impl SegmentDomain {
self.stroke.clear();
}
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
self.ids.push(id);
self.start_point.push(start);
self.end_point.push(end);
self.handles.push(handles);
self.stroke.push(stroke);
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool) {
let mut keep = self.ids.iter().map(&f);
self.start_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.end_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.handles.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.stroke.retain(|_| keep.next().unwrap_or_default());
self.ids.retain(f);
}
fn resolve_id(&self, id: SegmentId) -> Option<usize> {
pub fn ids(&self) -> &[SegmentId] {
&self.ids
}
pub fn next_id(&self) -> SegmentId {
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(SegmentId::ZERO)
}
pub fn start_point(&self) -> &[PointId] {
&self.start_point
}
pub fn end_point(&self) -> &[PointId] {
&self.end_point
}
pub fn handles(&self) -> &[bezier_rs::BezierHandles] {
&self.handles
}
pub fn stroke(&self) -> &[StrokeId] {
&self.stroke
}
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
if self.ids.contains(&id) {
warn!("Duplicate segment");
return;
}
// Attempt to keep line joins?
let after = self.end_point.iter().copied().position(|other_end| other_end == start || other_end == end);
let before = self.start_point.iter().copied().position(|other_start| other_start == start || other_start == end);
let (index, flip) = match (before, after) {
(_, Some(after)) => (after + 1, self.end_point[after] == end),
(Some(before), _) => (before, self.start_point[before] == start),
(None, None) => (self.ids.len(), false),
};
self.ids.insert(index, id);
self.start_point.insert(index, if flip { end } else { start });
self.end_point.insert(index, if flip { start } else { end });
self.handles.insert(index, if flip { handles.flipped() } else { handles });
self.stroke.insert(index, stroke);
}
pub fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
self.ids.iter().copied().zip(self.start_point.iter_mut())
}
pub fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
self.ids.iter().copied().zip(self.end_point.iter_mut())
}
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
nested.map(|(((&a, b), &c), &d)| (a, b, c, d))
}
pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {
self.ids.iter().copied().zip(self.stroke.iter_mut())
}
pub fn segment_start_from_id(&self, segment: SegmentId) -> Option<PointId> {
self.id_to_index(segment).and_then(|index| self.start_point.get(index)).copied()
}
pub fn segment_end_from_id(&self, segment: SegmentId) -> Option<PointId> {
self.id_to_index(segment).and_then(|index| self.end_point.get(index)).copied()
}
/// Returns an array for the start and end points of a segment.
pub fn points_from_id(&self, segment: SegmentId) -> Option<[PointId; 2]> {
self.segment_start_from_id(segment).and_then(|start| self.segment_end_from_id(segment).map(|end| [start, end]))
}
/// Attempts to find another point in the segment that is not the one passed in.
pub fn other_point(&self, segment: SegmentId, current: PointId) -> Option<PointId> {
self.points_from_id(segment).and_then(|points| points.into_iter().find(|&point| point != current))
}
/// Gets all points connected to the current one but not including the current one.
pub fn connected_points(&self, current: PointId) -> impl Iterator<Item = PointId> + '_ {
self.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) {
(true, false) => Some(b),
(false, true) => Some(a),
_ => None,
})
}
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
debug_assert_eq!(self.ids.len(), self.handles.len());
debug_assert_eq!(self.ids.len(), self.start_point.len());
debug_assert_eq!(self.ids.len(), self.end_point.len());
self.ids.iter().position(|&check_id| check_id == id)
}
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
match (self.resolve_id(*range.start()), self.resolve_id(*range.end())) {
(Some(start), Some(end)) => Some(start..=end),
match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
_ => {
warn!("Resolving range with invalid id");
None
@ -158,6 +282,26 @@ impl SegmentDomain {
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
}
}
/// Enumerate all segments that start at the point.
pub fn start_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
}
/// Enumerate all segments that end at the point.
pub fn end_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
}
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
pub fn all_connected(&self, point: PointId) -> impl Iterator<Item = HandleId> + '_ {
self.start_connected(point).map(HandleId::primary).chain(self.end_connected(point).map(HandleId::end))
}
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
pub fn connected_count(&self, point: PointId) -> usize {
self.all_connected(point).count()
}
}
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]
@ -184,7 +328,19 @@ impl RegionDomain {
self.fill.clear();
}
pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {
let mut keep = self.ids.iter().map(&f);
self.segment_range.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.fill.retain(|_| keep.next().unwrap_or_default());
self.ids.retain(&f);
}
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
if self.ids.contains(&id) {
warn!("Duplicate region");
return;
}
self.ids.push(id);
self.segment_range.push(segment_range);
self.fill.push(fill);
@ -194,6 +350,30 @@ impl RegionDomain {
self.ids.iter().position(|&check_id| check_id == id)
}
pub fn next_id(&self) -> RegionId {
self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
}
pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> {
self.ids.iter().copied().zip(self.segment_range.iter_mut())
}
pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {
self.ids.iter().copied().zip(self.fill.iter_mut())
}
pub fn ids(&self) -> &[RegionId] {
&self.ids
}
pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] {
&self.segment_range
}
pub fn fill(&self) -> &[FillId] {
&self.fill
}
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
self.segment_range.extend(
@ -209,15 +389,22 @@ impl RegionDomain {
impl super::VectorData {
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. Returns [`None`] if either ID is invalid.
fn segment_to_bezier(&self, start: PointId, end: PointId, handles: bezier_rs::BezierHandles) -> Option<bezier_rs::Bezier> {
let start = self.point_domain.pos_from_id(start)?;
let end = self.point_domain.pos_from_id(end)?;
let start = self.point_domain.position_from_id(start)?;
let end = self.point_domain.position_from_id(end)?;
Some(bezier_rs::Bezier { start, end, handles })
}
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
let index = self.segment_domain.resolve_id(id)?;
self.segment_to_bezier(self.segment_domain.start_point[index], self.segment_domain.end_point[index], self.segment_domain.handles[index])
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
}
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
let index: usize = self.segment_domain.id_to_index(id)?;
let start = self.segment_domain.start_point[index];
let end = self.segment_domain.end_point[index];
Some((start, end, self.segment_to_bezier(start, end, self.segment_domain.handles[index])?))
}
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
@ -237,17 +424,6 @@ impl super::VectorData {
let mut first_point = None;
let mut groups = Vec::new();
let mut last: Option<(PointId, bezier_rs::BezierHandles)> = None;
let end_point = |last: Option<(PointId, bezier_rs::BezierHandles)>, next: Option<PointId>, groups: &mut Vec<_>| {
if let Some((disconnected_previous, previous_handle)) = last.filter(|(end, _)| !next.is_some_and(|next| next == *end)) {
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.pos_from_id(disconnected_previous)?,
in_handle: previous_handle.end(),
out_handle: None,
id: disconnected_previous,
});
}
Some(())
};
for (handle, start, end) in segments {
if last.is_some_and(|(previous_end, _)| previous_end != start) {
@ -255,10 +431,9 @@ impl super::VectorData {
return None;
}
first_point = Some(first_point.unwrap_or(start));
end_point(last, Some(start), &mut groups)?;
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.pos_from_id(start)?,
anchor: self.point_domain.position_from_id(start)?,
in_handle: last.and_then(|(_, handle)| handle.end()),
out_handle: handle.start(),
id: start,
@ -266,8 +441,21 @@ impl super::VectorData {
last = Some((end, handle));
}
end_point(last, None, &mut groups)?;
let closed = groups.len() > 1 && last.map(|(point, _)| point) == first_point;
if let Some((end, last_handle)) = last {
if closed {
groups[0].in_handle = last_handle.end();
} else {
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.position_from_id(end)?,
in_handle: last_handle.end(),
out_handle: None,
id: end,
});
}
}
Some(bezier_rs::Subpath::new(groups, closed))
}
@ -279,10 +467,13 @@ impl super::VectorData {
.zip(&self.region_domain.segment_range)
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
.filter_map(|(id, range)| {
let segments_iter = self.segment_domain.handles[range.clone()]
let segments_iter = self
.segment_domain
.handles
.get(range.clone())?
.iter()
.zip(&self.segment_domain.start_point[range.clone()])
.zip(&self.segment_domain.end_point[range])
.zip(self.segment_domain.start_point.get(range.clone())?)
.zip(self.segment_domain.end_point.get(range)?)
.map(|((&handles, &start), &end)| (handles, start, end));
self.subpath_from_segments(segments_iter).map(|subpath| (id, subpath))
@ -294,6 +485,17 @@ impl super::VectorData {
StrokePathIter { vector_data: self, segment_index: 0 }
}
/// Construct an iterator [`bezier_rs::ManipulatorGroup`] for stroke.
pub fn manipulator_groups(&self) -> impl Iterator<Item = bezier_rs::ManipulatorGroup<PointId>> + '_ {
self.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))
}
/// Get manipulator by id
pub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<bezier_rs::ManipulatorGroup<PointId>> {
let id = id.into();
self.manipulator_groups().find(|group| group.id == id)
}
/// Transforms this vector data
pub fn transform(&mut self, transform: DAffine2) {
self.point_domain.transform(transform);
@ -301,6 +503,7 @@ impl super::VectorData {
}
}
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a super::VectorData,
segment_index: usize,
@ -339,11 +542,6 @@ impl bezier_rs::Identifier for PointId {
Self::generate()
}
}
impl From<crate::uuid::ManipulatorGroupId> for PointId {
fn from(value: crate::uuid::ManipulatorGroupId) -> Self {
Self(value.inner())
}
}
impl crate::vector::ConcatElement for super::VectorData {
fn concat(&mut self, other: &Self, transform: glam::DAffine2) {
@ -369,6 +567,7 @@ impl crate::vector::ConcatElement for super::VectorData {
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
struct IdMap {
point_map: HashMap<PointId, PointId>,
segment_map: HashMap<SegmentId, SegmentId>,

View file

@ -0,0 +1,530 @@
use super::*;
use crate::Node;
use bezier_rs::BezierHandles;
use dyn_any::{DynAny, StaticType};
use std::collections::{HashMap, HashSet};
/// Represents a procedural change to the [`PointDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointModification {
add: Vec<PointId>,
remove: HashSet<PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
delta: HashMap<PointId, DVec2>,
}
impl PointModification {
/// Apply this modification to the specified [`PointDomain`].
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
point_domain.retain(|id| !self.remove.contains(id));
for (id, position) in point_domain.positions_mut() {
let Some(&delta) = self.delta.get(&id) else { continue };
if !delta.is_finite() {
warn!("Invalid delta when applying a point modification");
continue;
}
*position += delta;
for (_, handles, start, end) in segment_domain.handles_mut() {
if start == id {
handles.move_start(delta);
}
if end == id {
handles.move_end(delta);
}
}
}
for &add_id in &self.add {
let Some(&position) = self.delta.get(&add_id) else { continue };
if !position.is_finite() {
warn!("Invalid position when applying a point modification");
continue;
}
point_domain.push(add_id, position);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.point_domain.ids().to_vec(),
remove: HashSet::new(),
delta: vector_data.point_domain.ids().iter().copied().zip(vector_data.point_domain.positions().iter().cloned()).collect(),
}
}
fn push(&mut self, id: PointId, position: DVec2) {
self.add.push(id);
self.delta.insert(id, position);
}
fn remove(&mut self, id: PointId) {
self.remove.insert(id);
self.add.retain(|&add| add != id);
self.delta.remove(&id);
}
}
/// Represents a procedural change to the [`SegmentDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SegmentModification {
add: Vec<SegmentId>,
remove: HashSet<SegmentId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
start_point: HashMap<SegmentId, PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
end_point: HashMap<SegmentId, PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
handle_primary: HashMap<SegmentId, Option<DVec2>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
handle_end: HashMap<SegmentId, Option<DVec2>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
stroke: HashMap<SegmentId, StrokeId>,
}
impl SegmentModification {
/// Apply this modification to the specified [`SegmentDomain`].
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
segment_domain.retain(|id| !self.remove.contains(id));
for (id, point) in segment_domain.start_point_mut() {
let Some(&new) = self.start_point.get(&id) else { continue };
if !point_domain.ids().contains(&new) {
warn!("Invalid start ID when applying a segment modification");
continue;
}
*point = new;
}
for (id, point) in segment_domain.end_point_mut() {
let Some(&new) = self.end_point.get(&id) else { continue };
if !point_domain.ids().contains(&new) {
warn!("Invalid end ID when applying a segment modification");
continue;
}
*point = new;
}
for (id, handles, start, end) in segment_domain.handles_mut() {
let Some(start) = point_domain.position_from_id(start) else { continue };
let Some(end) = point_domain.position_from_id(end) else { continue };
// Compute the actual start and end position based on the offset from the anchor
let start = self.handle_primary.get(&id).copied().map(|handle| handle.map(|handle| handle + start));
let end = self.handle_end.get(&id).copied().map(|handle| handle.map(|handle| handle + end));
if !start.unwrap_or_default().map_or(true, |start| start.is_finite()) || !end.unwrap_or_default().map_or(true, |end| end.is_finite()) {
warn!("Invalid handles when applying a segment modification");
continue;
}
match (start, end) {
// The new handles are fully specified by the modification
(Some(Some(handle_start)), Some(Some(handle_end))) => *handles = BezierHandles::Cubic { handle_start, handle_end },
(Some(Some(handle)), Some(None)) | (Some(None), Some(Some(handle))) => *handles = BezierHandles::Quadratic { handle },
(Some(None), Some(None)) => *handles = BezierHandles::Linear,
// Remove the end handle
(None, Some(None)) => {
if let BezierHandles::Cubic { handle_start, .. } = *handles {
*handles = BezierHandles::Quadratic { handle: handle_start }
}
}
// Change the end handle
(None, Some(Some(handle_end))) => match *handles {
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_end },
BezierHandles::Quadratic { handle: handle_start } => *handles = BezierHandles::Cubic { handle_start, handle_end },
BezierHandles::Cubic { handle_start, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
},
// Remove the start handle
(Some(None), None) => *handles = BezierHandles::Linear,
// Change the start handle
(Some(Some(handle_start)), None) => match *handles {
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_start },
BezierHandles::Quadratic { .. } => *handles = BezierHandles::Quadratic { handle: handle_start },
BezierHandles::Cubic { handle_end, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
},
// No change
(None, None) => {}
};
}
for (id, stroke) in segment_domain.stroke_mut() {
let Some(&new) = self.stroke.get(&id) else { continue };
*stroke = new;
}
for &add_id in &self.add {
let Some(&start) = self.start_point.get(&add_id) else { continue };
let Some(&end) = self.end_point.get(&add_id) else { continue };
let Some(&handle_start) = self.handle_primary.get(&add_id) else { continue };
let Some(&handle_end) = self.handle_end.get(&add_id) else { continue };
let Some(&stroke) = self.stroke.get(&add_id) else { continue };
if !point_domain.ids().contains(&start) {
warn!("invalid start id");
continue;
}
if !point_domain.ids().contains(&end) {
warn!("invalid end id");
continue;
}
let Some(start_position) = point_domain.position_from_id(start) else { continue };
let Some(end_position) = point_domain.position_from_id(end) else { continue };
let handles = match (handle_start, handle_end) {
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic {
handle_start: handle_start + start_position,
handle_end: handle_end + end_position,
},
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle: handle + start_position },
(None, None) => BezierHandles::Linear,
};
if !handles.is_finite() {
warn!("invalid handles");
continue;
}
segment_domain.push(add_id, start, end, handles, stroke);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.segment_domain.ids().to_vec(),
remove: HashSet::new(),
start_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.start_point().iter().cloned()).collect(),
end_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.end_point().iter().cloned()).collect(),
handle_primary: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.stroke().iter().cloned()).collect(),
}
}
fn push(&mut self, id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2], stroke: StrokeId) {
self.remove.remove(&id);
self.add.push(id);
self.start_point.insert(id, points[0]);
self.end_point.insert(id, points[1]);
self.handle_primary.insert(id, handles[0]);
self.handle_end.insert(id, handles[1]);
self.stroke.insert(id, stroke);
}
fn remove(&mut self, id: SegmentId) {
self.remove.insert(id);
self.add.retain(|&add| add != id);
self.start_point.remove(&id);
self.end_point.remove(&id);
self.handle_primary.remove(&id);
self.handle_end.remove(&id);
self.stroke.remove(&id);
}
}
/// Represents a procedural change to the [`RegionDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegionModification {
add: Vec<RegionId>,
remove: HashSet<RegionId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
segment_range: HashMap<RegionId, core::ops::RangeInclusive<SegmentId>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
fill: HashMap<RegionId, FillId>,
}
impl RegionModification {
/// Apply this modification to the specified [`RegionDomain`].
pub fn apply(&self, region_domain: &mut RegionDomain) {
region_domain.retain(|id| !self.remove.contains(id));
for (id, segment_range) in region_domain.segment_range_mut() {
let Some(new) = self.segment_range.get(&id) else { continue };
*segment_range = new.clone(); // Range inclusive is not copy
}
for (id, fill) in region_domain.fill_mut() {
let Some(&new) = self.fill.get(&id) else { continue };
*fill = new;
}
for &add_id in &self.add {
let Some(segment_range) = self.segment_range.get(&add_id) else { continue };
let Some(&fill) = self.fill.get(&add_id) else { continue };
region_domain.push(add_id, segment_range.clone(), fill);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.region_domain.ids().to_vec(),
remove: HashSet::new(),
segment_range: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.segment_range().iter().cloned()).collect(),
fill: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.fill().iter().cloned()).collect(),
}
}
}
/// Represents a procedural change to the [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VectorModification {
points: PointModification,
segments: SegmentModification,
regions: RegionModification,
add_g1_continuous: HashSet<[HandleId; 2]>,
remove_g1_continuous: HashSet<[HandleId; 2]>,
}
/// A modification type that can be added to a [`VectorModification`].
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorModificationType {
InsertSegment { id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2] },
InsertPoint { id: PointId, position: DVec2 },
RemoveSegment { id: SegmentId },
RemovePoint { id: PointId },
SetG1Continuous { handles: [HandleId; 2], enabled: bool },
SetHandles { segment: SegmentId, handles: [Option<DVec2>; 2] },
SetPrimaryHandle { segment: SegmentId, relative_position: DVec2 },
SetEndHandle { segment: SegmentId, relative_position: DVec2 },
SetStartPoint { segment: SegmentId, id: PointId },
SetEndPoint { segment: SegmentId, id: PointId },
ApplyPointDelta { point: PointId, delta: DVec2 },
ApplyPrimaryDelta { segment: SegmentId, delta: DVec2 },
ApplyEndDelta { segment: SegmentId, delta: DVec2 },
}
impl VectorModification {
/// Apply this modification to the specified [`VectorData`].
pub fn apply(&self, vector_data: &mut VectorData) {
self.points.apply(&mut vector_data.point_domain, &mut vector_data.segment_domain);
self.segments.apply(&mut vector_data.segment_domain, &vector_data.point_domain);
self.regions.apply(&mut vector_data.region_domain);
let valid = |val: &[HandleId; 2]| vector_data.segment_domain.ids().contains(&val[0].segment) && vector_data.segment_domain.ids().contains(&val[1].segment);
vector_data
.colinear_manipulators
.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));
for handles in &self.add_g1_continuous {
if !vector_data.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector_data.colinear_manipulators.push(*handles);
}
}
}
/// Add a [`VectorModificationType`] to this modification.
pub fn modify(&mut self, vector_data_modification: &VectorModificationType) {
match vector_data_modification {
VectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),
VectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),
VectorModificationType::RemoveSegment { id } => self.segments.remove(*id),
VectorModificationType::RemovePoint { id } => self.points.remove(*id),
VectorModificationType::SetG1Continuous { handles, enabled } => {
if *enabled {
if !self.add_g1_continuous.contains(&[handles[1], handles[0]]) {
self.add_g1_continuous.insert(*handles);
}
self.remove_g1_continuous.remove(handles);
self.remove_g1_continuous.remove(&[handles[1], handles[0]]);
} else {
if !self.remove_g1_continuous.contains(&[handles[1], handles[0]]) {
self.remove_g1_continuous.insert(*handles);
}
self.add_g1_continuous.remove(handles);
self.add_g1_continuous.remove(&[handles[1], handles[0]]);
}
}
VectorModificationType::SetHandles { segment, handles } => {
self.segments.handle_primary.insert(*segment, handles[0]);
self.segments.handle_end.insert(*segment, handles[1]);
}
VectorModificationType::SetPrimaryHandle { segment, relative_position } => {
self.segments.handle_primary.insert(*segment, Some(*relative_position));
}
VectorModificationType::SetEndHandle { segment, relative_position } => {
self.segments.handle_end.insert(*segment, Some(*relative_position));
}
VectorModificationType::SetStartPoint { segment, id } => {
self.segments.start_point.insert(*segment, *id);
}
VectorModificationType::SetEndPoint { segment, id } => {
self.segments.end_point.insert(*segment, *id);
}
VectorModificationType::ApplyPointDelta { point, delta } => {
*self.points.delta.entry(*point).or_default() += *delta;
}
VectorModificationType::ApplyPrimaryDelta { segment, delta } => {
let position = self.segments.handle_primary.entry(*segment).or_default();
*position = Some(position.unwrap_or_default() + *delta);
}
VectorModificationType::ApplyEndDelta { segment, delta } => {
let position = self.segments.handle_end.entry(*segment).or_default();
*position = Some(position.unwrap_or_default() + *delta);
}
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
points: PointModification::create_from_vector(vector_data),
segments: SegmentModification::create_from_vector(vector_data),
regions: RegionModification::create_from_vector(vector_data),
add_g1_continuous: vector_data.colinear_manipulators.iter().copied().collect(),
remove_g1_continuous: HashSet::new(),
}
}
}
impl core::hash::Hash for VectorModification {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
// TODO: properly implement (hashing a hashset is difficult because ordering is unstable)
PointId::generate().hash(state);
}
}
/// A node that applies a procedural modification to some [`VectorData`].
#[derive(Debug, Clone, Copy)]
pub struct PathModify<VectorModificationNode> {
modification: VectorModificationNode,
}
#[node_macro::node_fn(PathModify)]
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> VectorData {
modification.apply(&mut vector_data);
vector_data
}
#[test]
fn modify_new() {
let vector_data = VectorData::from_subpaths(
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
false,
);
let modify = VectorModification::create_from_vector(&vector_data);
let mut new = VectorData::empty();
modify.apply(&mut new);
assert_eq!(vector_data, new);
}
#[test]
fn modify_existing() {
use bezier_rs::{Bezier, Subpath};
let subpaths = [
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
Subpath::from_beziers(
&[
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
],
false,
),
];
let mut vector_data = VectorData::from_subpaths(&subpaths, false);
let mut modify_new = VectorModification::create_from_vector(&vector_data);
let mut modify_original = VectorModification::default();
for modification in [&mut modify_new, &mut modify_original] {
let point = vector_data.point_domain.ids()[0];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });
let point = vector_data.point_domain.ids()[9];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });
}
let mut new = VectorData::empty();
modify_new.apply(&mut new);
modify_original.apply(&mut vector_data);
assert_eq!(vector_data, new);
assert_eq!(vector_data.point_domain.positions()[0], DVec2::X);
assert_eq!(vector_data.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(
vector_data.segment_bezier_iter().nth(8).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))
);
assert_eq!(
vector_data.segment_bezier_iter().nth(9).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))
);
}
// TODO: Eventually remove this (probably starting late 2024)
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::hash::Hash;
fn serialize_hashmap<K, V, S>(hashmap: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
where
K: Serialize + Eq + Hash,
V: Serialize,
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(hashmap.len()))?;
for (key, value) in hashmap {
seq.serialize_element(&(key, value))?;
}
seq.end()
}
fn deserialize_hashmap<'de, K, V, D>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
D: Deserializer<'de>,
{
struct HashMapVisitor<K, V> {
marker: std::marker::PhantomData<fn() -> HashMap<K, V>>,
}
impl<'de, K, V> Visitor<'de> for HashMapVisitor<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
type Value = HashMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of tuples")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut hashmap = HashMap::new();
while let Some((key, value)) = seq.next_element()? {
hashmap.insert(key, value);
}
Ok(hashmap)
}
}
let visitor = HashMapVisitor { marker: std::marker::PhantomData };
deserializer.deserialize_seq(visitor)
}

View file

@ -170,11 +170,11 @@ fn solidify_stroke(vector_data: VectorData) -> VectorData {
// This is where we determine whether we have a closed or open path. Ex: Oval vs line segment.
if subpath_out.1.is_some() {
// Two closed subpaths, closed shape. Add both subpaths.
result.append_subpath(subpath_out.0);
result.append_subpath(subpath_out.1.unwrap());
result.append_subpath(subpath_out.0, false);
result.append_subpath(subpath_out.1.unwrap(), false);
} else {
// One closed subpath, open path.
result.append_subpath(subpath_out.0);
result.append_subpath(subpath_out.0, false);
}
}
@ -363,7 +363,7 @@ pub struct PoissonDiskPoints<SeparationDiskDiameter> {
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64) -> VectorData {
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let mut result = VectorData::empty();
for (_, mut subpath) in vector_data.region_bezier_paths() {
for mut subpath in vector_data.stroke_bezier_paths() {
if subpath.manipulator_groups().len() < 3 {
continue;
}
@ -400,6 +400,8 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
let first_handles = bezier_rs::solve_spline_first_handle(points.positions());
let stroke_id = StrokeId::ZERO;
for (start_index, end_index) in (0..(points.positions().len())).zip(1..(points.positions().len())) {
let handle_start = first_handles[start_index];
let handle_end = points.positions()[end_index] * 2. - first_handles[end_index];
@ -407,7 +409,7 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
vector_data
.segment_domain
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, StrokeId::generate())
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, stroke_id)
}
vector_data
@ -484,7 +486,7 @@ async fn morph<SourceFuture: Future<Output = VectorData>, TargetFuture: Future<O
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
}
result.append_subpath(source_path);
result.append_subpath(source_path, true);
}
// Mismatched subpath count
for mut source_path in source_paths {