Bézier-rs: Add ids to manipulator groups (#1054)

* Add ids to manipulator groups

* Fix tests

* Rename trait from ManipulatorGroupId to Identifier

* Rename EmptyManipulatorGroupId to EmptyId
This commit is contained in:
0HyperCube 2023-02-25 16:23:58 +00:00 committed by Keavon Chambers
parent 7a52e50a94
commit 08b2782917
8 changed files with 93 additions and 27 deletions

View file

@ -4,10 +4,10 @@ use crate::consts::*;
use std::fmt::Write;
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
impl Subpath {
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
pub fn new(manipulator_groups: Vec<ManipulatorGroup>, closed: bool) -> Self {
pub fn new(manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>, closed: bool) -> Self {
assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
Self { manipulator_groups, closed }
}
@ -20,11 +20,13 @@ impl Subpath {
anchor: bezier.start(),
in_handle: None,
out_handle: bezier.handle_start(),
id: ManipulatorGroupId::new(),
},
ManipulatorGroup {
anchor: bezier.end(),
in_handle: bezier.handle_end(),
out_handle: None,
id: ManipulatorGroupId::new(),
},
],
false,
@ -59,7 +61,7 @@ impl Subpath {
}
/// Returns an iterator of the [Bezier]s along the `Subpath`.
pub fn iter(&self) -> SubpathIter {
pub fn iter(&self) -> SubpathIter<ManipulatorGroupId> {
SubpathIter { sub_path: self, index: 0 }
}

View file

@ -5,7 +5,7 @@ use crate::ProjectionOptions;
use glam::DVec2;
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
impl Subpath {
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`.
/// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`.
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/length/solo" title="Length Demo"></iframe>
@ -126,16 +126,19 @@ mod tests {
anchor: start,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle3),
id: EmptyId,
},
],
false,
@ -165,16 +168,19 @@ mod tests {
anchor: start,
in_handle: Some(handle3),
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: middle,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
],
false,
@ -191,6 +197,7 @@ mod tests {
anchor: DVec2::new(0., 0.),
in_handle: None,
out_handle: None,
id: EmptyId,
};
let open_subpath = Subpath {
manipulator_groups: vec![mock_manipulator_group; 5],
@ -212,6 +219,7 @@ mod tests {
anchor: DVec2::new(0., 0.),
in_handle: None,
out_handle: None,
id: EmptyId,
};
let closed_subpath = Subpath {
manipulator_groups: vec![mock_manipulator_group; 5],

View file

@ -3,7 +3,7 @@ use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
use crate::utils::f64_compare;
use crate::{SubpathTValue, TValue};
impl Subpath {
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// Inserts a `ManipulatorGroup` at a certain point along the subpath based on the parametric `t`-value provided.
/// Expects `t` to be within the inclusive range `[0, 1]`.
pub fn insert(&mut self, t: SubpathTValue) {
@ -22,6 +22,7 @@ impl Subpath {
anchor: first.end(),
in_handle: first.handle_end(),
out_handle: second.handle_start(),
id: ManipulatorGroupId::new(),
};
let number_of_groups = self.manipulator_groups.len() + 1;
self.manipulator_groups.insert((segment_index) + 1, new_group);
@ -37,7 +38,7 @@ mod tests {
use super::*;
use glam::DVec2;
fn set_up_open_subpath() -> Subpath {
fn set_up_open_subpath() -> Subpath<EmptyId> {
let start = DVec2::new(20., 30.);
let middle1 = DVec2::new(80., 90.);
let middle2 = DVec2::new(100., 100.);
@ -53,28 +54,32 @@ mod tests {
anchor: start,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle1,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle2,
in_handle: None,
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle3),
id: EmptyId,
},
],
false,
)
}
fn set_up_closed_subpath() -> Subpath {
fn set_up_closed_subpath() -> Subpath<EmptyId> {
let mut subpath = set_up_open_subpath();
subpath.closed = true;
subpath

View file

@ -12,19 +12,19 @@ use std::ops::{Index, IndexMut};
/// Structure used to represent a path composed of [Bezier] curves.
#[derive(Clone, PartialEq)]
pub struct Subpath {
manipulator_groups: Vec<ManipulatorGroup>,
pub struct Subpath<ManipulatorGroupId: crate::Identifier> {
manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>,
closed: bool,
}
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
pub struct SubpathIter<'a> {
pub struct SubpathIter<'a, ManipulatorGroupId: crate::Identifier> {
index: usize,
sub_path: &'a Subpath,
sub_path: &'a Subpath<ManipulatorGroupId>,
}
impl Index<usize> for Subpath {
type Output = ManipulatorGroup;
impl<ManipulatorGroupId: crate::Identifier> Index<usize> for Subpath<ManipulatorGroupId> {
type Output = ManipulatorGroup<ManipulatorGroupId>;
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
@ -32,14 +32,14 @@ impl Index<usize> for Subpath {
}
}
impl IndexMut<usize> for Subpath {
impl<ManipulatorGroupId: crate::Identifier> IndexMut<usize> for Subpath<ManipulatorGroupId> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
&mut self.manipulator_groups[index]
}
}
impl Iterator for SubpathIter<'_> {
impl<ManipulatorGroupId: crate::Identifier> Iterator for SubpathIter<'_, ManipulatorGroupId> {
type Item = Bezier;
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.

View file

@ -5,7 +5,7 @@ use crate::TValue;
use glam::DVec2;
impl Subpath {
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// Calculate the point on the subpath based on the parametric `t`-value provided.
/// Expects `t` to be within the inclusive range `[0, 1]`.
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#subpath/evaluate/solo" title="Evaluate Demo"></iframe>
@ -144,11 +144,13 @@ mod tests {
anchor: start,
in_handle: None,
out_handle: Some(handle),
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle),
id: EmptyId,
},
],
false,
@ -186,16 +188,19 @@ mod tests {
anchor: start,
in_handle: Some(handle3),
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: middle,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
],
false,
@ -290,16 +295,19 @@ mod tests {
anchor: cubic_start,
in_handle: None,
out_handle: Some(cubic_handle_1),
id: EmptyId,
},
ManipulatorGroup {
anchor: cubic_end,
in_handle: Some(cubic_handle_2),
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: quadratic_end,
in_handle: Some(quadratic_1_handle),
out_handle: Some(quadratic_2_handle),
id: EmptyId,
},
],
true,
@ -366,16 +374,19 @@ mod tests {
anchor: cubic_start,
in_handle: None,
out_handle: Some(cubic_handle_1),
id: EmptyId,
},
ManipulatorGroup {
anchor: cubic_end,
in_handle: Some(cubic_handle_2),
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: quadratic_end,
in_handle: Some(quadratic_1_handle),
out_handle: Some(quadratic_2_handle),
id: EmptyId,
},
],
true,
@ -431,16 +442,19 @@ mod tests {
anchor: cubic_start,
in_handle: None,
out_handle: Some(cubic_handle_1),
id: EmptyId,
},
ManipulatorGroup {
anchor: cubic_end,
in_handle: Some(cubic_handle_2),
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: quadratic_end,
in_handle: Some(quadratic_1_handle),
out_handle: Some(quadratic_2_handle),
id: EmptyId,
},
],
true,

View file

@ -3,15 +3,33 @@ use super::Bezier;
use glam::DVec2;
use std::fmt::{Debug, Formatter, Result};
/// An id type used for each [ManipulatorGroup].
pub trait Identifier: Sized + Clone + PartialEq {
fn new() -> Self;
}
/// An empty id type for use in tests
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg(test)]
pub(crate) struct EmptyId;
#[cfg(test)]
impl Identifier for EmptyId {
fn new() -> Self {
Self
}
}
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
#[derive(Copy, Clone, PartialEq)]
pub struct ManipulatorGroup {
pub struct ManipulatorGroup<ManipulatorGroupId: crate::Identifier> {
pub anchor: DVec2,
pub in_handle: Option<DVec2>,
pub out_handle: Option<DVec2>,
pub id: ManipulatorGroupId,
}
impl Debug for ManipulatorGroup {
impl<ManipulatorGroupId: crate::Identifier> Debug for ManipulatorGroup<ManipulatorGroupId> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if self.in_handle.is_some() && self.out_handle.is_some() {
write!(f, "anchor: {}, in: {}, out: {}", self.anchor, self.in_handle.unwrap(), self.out_handle.unwrap())
@ -25,8 +43,8 @@ impl Debug for ManipulatorGroup {
}
}
impl ManipulatorGroup {
pub fn to_bezier(&self, end_group: &ManipulatorGroup) -> Bezier {
impl<ManipulatorGroupId: crate::Identifier> ManipulatorGroup<ManipulatorGroupId> {
pub fn to_bezier(&self, end_group: &ManipulatorGroup<ManipulatorGroupId>) -> Bezier {
let start = self.anchor;
let end = end_group.anchor;
let out_handle = self.out_handle;

View file

@ -3,12 +3,12 @@ use crate::utils::SubpathTValue;
use crate::utils::TValue;
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
impl Subpath {
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
/// If the original Subpath was closed, a single open Subpath will be returned.
/// If the original Subpath was open, two open Subpaths will be returned.
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#subpath/split/solo" title="Split Demo"></iframe>
pub fn split(&self, t: SubpathTValue) -> (Subpath, Option<Subpath>) {
pub fn split(&self, t: SubpathTValue) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
let (segment_index, t) = self.t_value_to_parametric(t);
let curve = self.get_segment(segment_index).unwrap();
@ -33,6 +33,7 @@ impl Subpath {
anchor: first_bezier.end(),
in_handle: last_curve.handle_end(),
out_handle: None,
id: ManipulatorGroupId::new(),
});
} else {
if !first_split.is_empty() {
@ -52,6 +53,7 @@ impl Subpath {
anchor: first_bezier.end(),
in_handle: first_bezier.handle_end(),
out_handle: None,
id: ManipulatorGroupId::new(),
});
}
@ -62,6 +64,7 @@ impl Subpath {
anchor: second_bezier.start(),
in_handle: None,
out_handle: second_bezier.handle_start(),
id: ManipulatorGroupId::new(),
},
);
}
@ -84,7 +87,7 @@ mod tests {
use super::*;
use glam::DVec2;
fn set_up_open_subpath() -> Subpath {
fn set_up_open_subpath() -> Subpath<EmptyId> {
let start = DVec2::new(20., 30.);
let middle1 = DVec2::new(80., 90.);
let middle2 = DVec2::new(100., 100.);
@ -100,28 +103,32 @@ mod tests {
anchor: start,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle1,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle2,
in_handle: None,
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle3),
id: EmptyId,
},
],
false,
)
}
fn set_up_closed_subpath() -> Subpath {
fn set_up_closed_subpath() -> Subpath<EmptyId> {
let mut subpath = set_up_open_subpath();
subpath.closed = true;
subpath
@ -154,7 +161,8 @@ mod tests {
ManipulatorGroup {
anchor: location,
in_handle: None,
out_handle: None
out_handle: None,
id: EmptyId,
}
);
assert_eq!(first.manipulator_groups.len(), 1);
@ -177,7 +185,8 @@ mod tests {
ManipulatorGroup {
anchor: location,
in_handle: None,
out_handle: None
out_handle: None,
id: EmptyId,
}
);
assert_eq!(second.manipulator_groups.len(), 1);