Bezier-rs: Updated Bezier function signatures to accept TValue (#967)

* Create helper for converting d to t values

* Add euclidean option for tangent and normal

* Modified bezier functions signatures to accept ComputeType

* Stylistic changes per review

* Added ComputeType documentation

* Renamed ComputeType to TValue

* Fixed comments

* Fixed failing unit tests

* Code review

* Fix comments in code review

* Renamed compute_type_to_parametric to t_value_to_parametric

---------

Co-authored-by: Linda Zheng <thelindazheng@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Rob Nadal
2023-02-13 12:31:51 -05:00
committed by Keavon Chambers
co-authored by Linda Zheng Keavon Chambers
parent f0ad4c91d3
commit a64c856ec4
25 changed files with 456 additions and 433 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
use super::*;
use crate::{ComputeType, ProjectionOptions};
use crate::{ProjectionOptions, TValue};
use glam::DVec2;
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
@@ -22,7 +22,7 @@ impl Subpath {
.iter()
.map(|bezier| {
let project_t = bezier.project(point, options);
(bezier.evaluate(ComputeType::Parametric(project_t)).distance(point), project_t)
(bezier.evaluate(TValue::Parametric(project_t)).distance(point), project_t)
})
.enumerate()
.min_by(|(_, (distance1, _)), (_, (distance2, _))| distance1.total_cmp(distance2))
+19 -19
View File
@@ -1,14 +1,14 @@
use super::*;
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
use crate::utils::f64_compare;
use crate::ComputeType;
use crate::TValue;
impl Subpath {
/// 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: ComputeType) {
pub fn insert(&mut self, t: TValue) {
match t {
ComputeType::Parametric(t) => {
TValue::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
let number_of_curves = self.len_segments() as f64;
@@ -25,7 +25,7 @@ impl Subpath {
// But the above if case would catch that, since `target_curve_t` would be 0.
let curve = self.iter().nth(target_curve_index as usize).unwrap();
let [first, second] = curve.split(target_curve_t);
let [first, second] = curve.split(TValue::Parametric(target_curve_t));
let new_group = ManipulatorGroup {
anchor: first.end(),
in_handle: first.handle_end(),
@@ -37,8 +37,8 @@ impl Subpath {
self.manipulator_groups[((target_curve_index as usize) + 2) % number_of_groups].in_handle = second.handle_end();
}
// TODO: change this implementation to Euclidean compute
ComputeType::Euclidean(_t) => {}
ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(),
TValue::Euclidean(_t) => {}
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
}
}
}
@@ -94,9 +94,9 @@ mod tests {
#[test]
fn insert_in_first_segment_of_open_subpath() {
let mut subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split((0.2 * 3.) % 1.);
subpath.insert(ComputeType::Parametric(0.2));
let location = subpath.evaluate(TValue::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 3.) % 1.));
subpath.insert(TValue::Parametric(0.2));
assert_eq!(subpath.manipulator_groups[1].anchor, location);
assert_eq!(split_pair[0], subpath.iter().next().unwrap());
assert_eq!(split_pair[1], subpath.iter().nth(1).unwrap());
@@ -105,9 +105,9 @@ mod tests {
#[test]
fn insert_in_last_segment_of_open_subpath() {
let mut subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.9));
let split_pair = subpath.iter().nth(2).unwrap().split((0.9 * 3.) % 1.);
subpath.insert(ComputeType::Parametric(0.9));
let location = subpath.evaluate(TValue::Parametric(0.9));
let split_pair = subpath.iter().nth(2).unwrap().split(TValue::Parametric((0.9 * 3.) % 1.));
subpath.insert(TValue::Parametric(0.9));
assert_eq!(subpath.manipulator_groups[3].anchor, location);
assert_eq!(split_pair[0], subpath.iter().nth(2).unwrap());
assert_eq!(split_pair[1], subpath.iter().nth(3).unwrap());
@@ -117,8 +117,8 @@ mod tests {
fn insert_at_exisiting_manipulator_group_of_open_subpath() {
// This will do nothing to the subpath
let mut subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.75));
subpath.insert(ComputeType::Parametric(0.75));
let location = subpath.evaluate(TValue::Parametric(0.75));
subpath.insert(TValue::Parametric(0.75));
assert_eq!(subpath.manipulator_groups[3].anchor, location);
assert_eq!(subpath.manipulator_groups.len(), 5);
assert_eq!(subpath.len_segments(), 4);
@@ -127,9 +127,9 @@ mod tests {
#[test]
fn insert_at_last_segment_of_closed_subpath() {
let mut subpath = set_up_closed_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.9));
let split_pair = subpath.iter().nth(3).unwrap().split((0.9 * 4.) % 1.);
subpath.insert(ComputeType::Parametric(0.9));
let location = subpath.evaluate(TValue::Parametric(0.9));
let split_pair = subpath.iter().nth(3).unwrap().split(TValue::Parametric((0.9 * 4.) % 1.));
subpath.insert(TValue::Parametric(0.9));
assert_eq!(subpath.manipulator_groups[4].anchor, location);
assert_eq!(split_pair[0], subpath.iter().nth(3).unwrap());
assert_eq!(split_pair[1], subpath.iter().nth(4).unwrap());
@@ -140,8 +140,8 @@ mod tests {
fn insert_at_last_manipulator_group_of_closed_subpath() {
// This will do nothing to the subpath
let mut subpath = set_up_closed_subpath();
let location = subpath.evaluate(ComputeType::Parametric(1.));
subpath.insert(ComputeType::Parametric(1.));
let location = subpath.evaluate(TValue::Parametric(1.));
subpath.insert(TValue::Parametric(1.));
assert_eq!(subpath.manipulator_groups[0].anchor, location);
assert_eq!(subpath.manipulator_groups.len(), 4);
assert!(subpath.closed);
+51 -61
View File
@@ -1,26 +1,26 @@
use super::*;
use crate::consts::MIN_SEPERATION_VALUE;
use crate::ComputeType;
use crate::TValue;
use glam::DVec2;
impl Subpath {
/// Calculate the point on the subpath based on the parametric `t`-value provided.
/// Expects `t` to be within the inclusive range `[0, 1]`.
pub fn evaluate(&self, t: ComputeType) -> DVec2 {
pub fn evaluate(&self, t: TValue) -> DVec2 {
match t {
ComputeType::Parametric(t) => {
TValue::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
if let (Some(curve), target_curve_t) = self.find_curve_parametric(t) {
curve.evaluate(ComputeType::Parametric(target_curve_t))
curve.evaluate(TValue::Parametric(target_curve_t))
} else {
self.iter().last().unwrap().evaluate(ComputeType::Parametric(1.))
self.iter().last().unwrap().evaluate(TValue::Parametric(1.))
}
}
// TODO: change this implementation to Euclidean compute
ComputeType::Euclidean(_t) => self.iter().next().unwrap().evaluate(ComputeType::Parametric(0.)),
ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(),
TValue::Euclidean(_t) => self.iter().next().unwrap().evaluate(TValue::Parametric(0.)),
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
}
}
@@ -54,35 +54,35 @@ impl Subpath {
intersection_t_values
}
pub fn tangent(&self, t: ComputeType) -> DVec2 {
pub fn tangent(&self, t: TValue) -> DVec2 {
match t {
ComputeType::Parametric(t) => {
TValue::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
if let (Some(curve), target_curve_t) = self.find_curve_parametric(t) {
curve.tangent(target_curve_t)
curve.tangent(TValue::Parametric(target_curve_t))
} else {
self.iter().last().unwrap().tangent(1.)
self.iter().last().unwrap().tangent(TValue::Parametric(1.))
}
}
ComputeType::Euclidean(_t) => unimplemented!(),
ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(),
TValue::Euclidean(_t) => unimplemented!(),
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
}
}
pub fn normal(&self, t: ComputeType) -> DVec2 {
pub fn normal(&self, t: TValue) -> DVec2 {
match t {
ComputeType::Parametric(t) => {
TValue::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
if let (Some(curve), target_curve_t) = self.find_curve_parametric(t) {
curve.normal(target_curve_t)
curve.normal(TValue::Parametric(target_curve_t))
} else {
self.iter().last().unwrap().normal(1.)
self.iter().last().unwrap().normal(TValue::Parametric(1.))
}
}
ComputeType::Euclidean(_t) => unimplemented!(),
ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(),
TValue::Euclidean(_t) => unimplemented!(),
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
}
}
}
@@ -124,16 +124,16 @@ mod tests {
);
let t0 = 0.;
assert_eq!(subpath.evaluate(ComputeType::Parametric(t0)), bezier.evaluate(ComputeType::Parametric(t0)));
assert_eq!(subpath.evaluate(TValue::Parametric(t0)), bezier.evaluate(TValue::Parametric(t0)));
let t1 = 0.25;
assert_eq!(subpath.evaluate(ComputeType::Parametric(t1)), bezier.evaluate(ComputeType::Parametric(t1)));
assert_eq!(subpath.evaluate(TValue::Parametric(t1)), bezier.evaluate(TValue::Parametric(t1)));
let t2 = 0.50;
assert_eq!(subpath.evaluate(ComputeType::Parametric(t2)), bezier.evaluate(ComputeType::Parametric(t2)));
assert_eq!(subpath.evaluate(TValue::Parametric(t2)), bezier.evaluate(TValue::Parametric(t2)));
let t3 = 1.;
assert_eq!(subpath.evaluate(ComputeType::Parametric(t3)), bezier.evaluate(ComputeType::Parametric(t3)));
assert_eq!(subpath.evaluate(TValue::Parametric(t3)), bezier.evaluate(TValue::Parametric(t3)));
}
#[test]
@@ -176,43 +176,38 @@ mod tests {
let t0 = 0.;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t0)),
linear_bezier.evaluate(ComputeType::Parametric(normalize_t(n, t0))),
subpath.evaluate(TValue::Parametric(t0)),
linear_bezier.evaluate(TValue::Parametric(normalize_t(n, t0))),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
let t1 = 0.25;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t1)),
linear_bezier.evaluate(ComputeType::Parametric(normalize_t(n, t1))),
subpath.evaluate(TValue::Parametric(t1)),
linear_bezier.evaluate(TValue::Parametric(normalize_t(n, t1))),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
let t2 = 0.50;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t2)),
quadratic_bezier.evaluate(ComputeType::Parametric(normalize_t(n, t2))),
subpath.evaluate(TValue::Parametric(t2)),
quadratic_bezier.evaluate(TValue::Parametric(normalize_t(n, t2))),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
let t3 = 0.75;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t3)),
quadratic_bezier.evaluate(ComputeType::Parametric(normalize_t(n, t3))),
subpath.evaluate(TValue::Parametric(t3)),
quadratic_bezier.evaluate(TValue::Parametric(normalize_t(n, t3))),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
let t4 = 1.0;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t4)),
quadratic_bezier.evaluate(ComputeType::Parametric(1.)),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(subpath.evaluate(TValue::Parametric(t4)), quadratic_bezier.evaluate(TValue::Parametric(1.)), MAX_ABSOLUTE_DIFFERENCE).all());
// Test closed subpath
@@ -221,19 +216,14 @@ mod tests {
let t5 = 2. / 3.;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t5)),
cubic_bezier.evaluate(ComputeType::Parametric(normalize_t(n, t5))),
subpath.evaluate(TValue::Parametric(t5)),
cubic_bezier.evaluate(TValue::Parametric(normalize_t(n, t5))),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
let t6 = 1.;
assert!(utils::dvec2_compare(
subpath.evaluate(ComputeType::Parametric(t6)),
cubic_bezier.evaluate(ComputeType::Parametric(1.)),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(subpath.evaluate(TValue::Parametric(t6)), cubic_bezier.evaluate(TValue::Parametric(1.)), MAX_ABSOLUTE_DIFFERENCE).all());
}
#[test]
@@ -281,22 +271,22 @@ mod tests {
let subpath_intersections = subpath.intersections(&line, None, None);
assert!(utils::dvec2_compare(
cubic_bezier.evaluate(ComputeType::Parametric(cubic_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[0])),
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[0])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(
quadratic_bezier_1.evaluate(ComputeType::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[1])),
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[1])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(
quadratic_bezier_1.evaluate(ComputeType::Parametric(quadratic_1_intersections[1])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[2])),
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[1])),
subpath.evaluate(TValue::Parametric(subpath_intersections[2])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
@@ -348,15 +338,15 @@ mod tests {
let subpath_intersections = subpath.intersections(&line, None, None);
assert!(utils::dvec2_compare(
cubic_bezier.evaluate(ComputeType::Parametric(cubic_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[0])),
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[0])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(
quadratic_bezier_1.evaluate(ComputeType::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[1])),
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[1])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
@@ -407,22 +397,22 @@ mod tests {
let subpath_intersections = subpath.intersections(&line, None, None);
assert!(utils::dvec2_compare(
cubic_bezier.evaluate(ComputeType::Parametric(cubic_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[0])),
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[0])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(
quadratic_bezier_1.evaluate(ComputeType::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[1])),
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])),
subpath.evaluate(TValue::Parametric(subpath_intersections[1])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
assert!(utils::dvec2_compare(
quadratic_bezier_1.evaluate(ComputeType::Parametric(quadratic_1_intersections[1])),
subpath.evaluate(ComputeType::Parametric(subpath_intersections[2])),
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[1])),
subpath.evaluate(TValue::Parametric(subpath_intersections[2])),
MAX_ABSOLUTE_DIFFERENCE
)
.all());
+22 -22
View File
@@ -1,14 +1,14 @@
use super::*;
use crate::ComputeType;
use crate::TValue;
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
impl Subpath {
/// 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.
pub fn split(&self, t: ComputeType) -> (Subpath, Option<Subpath>) {
pub fn split(&self, t: TValue) -> (Subpath, Option<Subpath>) {
match t {
ComputeType::Parametric(t) => {
TValue::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
let number_of_curves = self.len_segments() as f64;
@@ -22,7 +22,7 @@ impl Subpath {
let optional_curve = self.iter().nth(target_curve_index as usize);
let curve = optional_curve.unwrap_or_else(|| self.iter().last().unwrap());
let [first_bezier, second_bezier] = curve.split(if t == 1. { t } else { target_curve_t });
let [first_bezier, second_bezier] = curve.split(TValue::Parametric(if t == 1. { t } else { target_curve_t }));
let mut clone = self.manipulator_groups.clone();
let (mut first_split, mut second_split) = if t > 0. {
@@ -83,8 +83,8 @@ impl Subpath {
}
}
// TODO: change this implementation to Euclidean compute
ComputeType::Euclidean(_t) => todo!(),
ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(),
TValue::Euclidean(_t) => todo!(),
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
}
}
}
@@ -140,9 +140,9 @@ mod tests {
#[test]
fn split_an_open_subpath() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split((0.2 * 3.) % 1.);
let (first, second) = subpath.split(ComputeType::Parametric(0.2));
let location = subpath.evaluate(TValue::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 3.) % 1.));
let (first, second) = subpath.split(TValue::Parametric(0.2));
assert!(second.is_some());
let second = second.unwrap();
assert_eq!(first.manipulator_groups[1].anchor, location);
@@ -154,9 +154,9 @@ mod tests {
#[test]
fn split_at_start_of_an_open_subpath() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.));
let split_pair = subpath.iter().next().unwrap().split(0.);
let (first, second) = subpath.split(ComputeType::Parametric(0.));
let location = subpath.evaluate(TValue::Parametric(0.));
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric(0.));
let (first, second) = subpath.split(TValue::Parametric(0.));
assert!(second.is_some());
let second = second.unwrap();
assert_eq!(
@@ -175,9 +175,9 @@ mod tests {
#[test]
fn split_at_end_of_an_open_subpath() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(ComputeType::Parametric(1.));
let split_pair = subpath.iter().last().unwrap().split(1.);
let (first, second) = subpath.split(ComputeType::Parametric(1.));
let location = subpath.evaluate(TValue::Parametric(1.));
let split_pair = subpath.iter().last().unwrap().split(TValue::Parametric(1.));
let (first, second) = subpath.split(TValue::Parametric(1.));
assert!(second.is_some());
let second = second.unwrap();
assert_eq!(first.manipulator_groups[3].anchor, location);
@@ -196,9 +196,9 @@ mod tests {
#[test]
fn split_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split((0.2 * 4.) % 1.);
let (first, second) = subpath.split(ComputeType::Parametric(0.2));
let location = subpath.evaluate(TValue::Parametric(0.2));
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 4.) % 1.));
let (first, second) = subpath.split(TValue::Parametric(0.2));
assert!(second.is_none());
assert_eq!(first.manipulator_groups[0].anchor, location);
assert_eq!(first.manipulator_groups[5].anchor, location);
@@ -210,8 +210,8 @@ mod tests {
#[test]
fn split_at_start_of_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let location = subpath.evaluate(ComputeType::Parametric(0.));
let (first, second) = subpath.split(ComputeType::Parametric(0.));
let location = subpath.evaluate(TValue::Parametric(0.));
let (first, second) = subpath.split(TValue::Parametric(0.));
assert!(second.is_none());
assert_eq!(first.manipulator_groups[0].anchor, location);
assert_eq!(first.manipulator_groups[4].anchor, location);
@@ -224,8 +224,8 @@ mod tests {
#[test]
fn split_at_end_of_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let location = subpath.evaluate(ComputeType::Parametric(1.));
let (first, second) = subpath.split(ComputeType::Parametric(1.));
let location = subpath.evaluate(TValue::Parametric(1.));
let (first, second) = subpath.split(TValue::Parametric(1.));
assert!(second.is_none());
assert_eq!(first.manipulator_groups[0].anchor, location);
assert_eq!(first.manipulator_groups[4].anchor, location);