mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-06 12:18:11 +08:00
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:
co-authored by
Linda Zheng
Keavon Chambers
parent
1c2b8f67b2
commit
76be1f8515
@@ -1,14 +1,15 @@
|
||||
use super::*;
|
||||
use crate::utils::{f64_compare, ComputeType};
|
||||
use crate::utils::{f64_compare, TValue};
|
||||
|
||||
use glam::DMat2;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// Functionality that transform Beziers, such as split, reduce, offset, etc.
|
||||
impl Bezier {
|
||||
/// Returns the pair of Bezier curves that result from splitting the original curve at the point corresponding to `t`.
|
||||
pub fn split(&self, t: f64) -> [Bezier; 2] {
|
||||
let split_point = self.evaluate(ComputeType::Parametric(t));
|
||||
/// Returns the pair of Bezier curves that result from splitting the original curve at the point `t` along the curve.
|
||||
pub fn split(&self, t: TValue) -> [Bezier; 2] {
|
||||
let t = self.t_value_to_parametric(t);
|
||||
let split_point = self.evaluate(TValue::Parametric(t));
|
||||
|
||||
match self.handles {
|
||||
BezierHandles::Linear => [Bezier::from_linear_dvec2(self.start, split_point), Bezier::from_linear_dvec2(split_point, self.end)],
|
||||
@@ -49,11 +50,13 @@ impl Bezier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Bezier curve representing the sub-curve starting at the point corresponding to `t1` and ending at the point corresponding to `t2`.
|
||||
pub fn trim(&self, t1: f64, t2: f64) -> Bezier {
|
||||
/// Returns the Bezier curve representing the sub-curve starting at the point `t1` and ending at the point `t2` along the curve.
|
||||
/// When `t1 < t2`, returns the reversed sub-curve starting at `t2` and ending at `t1`.
|
||||
pub fn trim(&self, t1: TValue, t2: TValue) -> Bezier {
|
||||
let (t1, t2) = (self.t_value_to_parametric(t1), self.t_value_to_parametric(t2));
|
||||
// If t1 is equal to t2, return a bezier comprised entirely of the same point
|
||||
if f64_compare(t1, t2, MAX_ABSOLUTE_DIFFERENCE) {
|
||||
let point = self.evaluate(ComputeType::Parametric(t1));
|
||||
let point = self.evaluate(TValue::Parametric(t1));
|
||||
return match self.handles {
|
||||
BezierHandles::Linear => Bezier::from_linear_dvec2(point, point),
|
||||
BezierHandles::Quadratic { handle: _ } => Bezier::from_quadratic_dvec2(point, point, point),
|
||||
@@ -63,7 +66,7 @@ impl Bezier {
|
||||
// Depending on the order of `t1` and `t2`, determine which half of the split we need to keep
|
||||
let t1_split_side = usize::from(t1 <= t2);
|
||||
let t2_split_side = usize::from(t1 > t2);
|
||||
let bezier_starting_at_t1 = self.split(t1)[t1_split_side];
|
||||
let bezier_starting_at_t1 = self.split(TValue::Parametric(t1))[t1_split_side];
|
||||
// Adjust the ratio `t2` to its corresponding value on the new curve that was split on `t1`
|
||||
let adjusted_t2 = if t1 < t2 || t1 == 0. {
|
||||
// Case where we took the split from t1 to the end
|
||||
@@ -73,7 +76,7 @@ impl Bezier {
|
||||
// Case where we took the split from the beginning to `t1`
|
||||
t2 / t1
|
||||
};
|
||||
let result = bezier_starting_at_t1.split(adjusted_t2)[t2_split_side];
|
||||
let result = bezier_starting_at_t1.split(TValue::Parametric(adjusted_t2))[t2_split_side];
|
||||
if t2 < t1 {
|
||||
return result.reverse();
|
||||
}
|
||||
@@ -132,8 +135,8 @@ impl Bezier {
|
||||
}
|
||||
}
|
||||
// Verify the angle formed by the endpoint normals is sufficiently small, ensuring the on-curve point for `t = 0.5` occurs roughly in the center of the polygon.
|
||||
let normal_0 = self.normal(0.);
|
||||
let normal_1 = self.normal(1.);
|
||||
let normal_0 = self.normal(TValue::Parametric(0.));
|
||||
let normal_1 = self.normal(TValue::Parametric(1.));
|
||||
let endpoint_normal_angle = (normal_0.x * normal_1.x + normal_0.y * normal_1.y).acos();
|
||||
endpoint_normal_angle < SCALABLE_CURVE_MAX_ENDPOINT_NORMAL_ANGLE
|
||||
}
|
||||
@@ -169,7 +172,7 @@ impl Bezier {
|
||||
extrema.windows(2).for_each(|t_pair| {
|
||||
let t_subcurve_start = t_pair[0];
|
||||
let t_subcurve_end = t_pair[1];
|
||||
let subcurve = self.trim(t_subcurve_start, t_subcurve_end);
|
||||
let subcurve = self.trim(TValue::Parametric(t_subcurve_start), TValue::Parametric(t_subcurve_end));
|
||||
// Perform no processing on the subcurve if it's already scalable.
|
||||
if subcurve.is_scalable() {
|
||||
result_beziers.push(subcurve);
|
||||
@@ -177,7 +180,7 @@ impl Bezier {
|
||||
return;
|
||||
}
|
||||
// According to <https://pomax.github.io/bezierinfo/#offsetting>, it is generally sufficient to split subcurves with no local extrema at `t = 0.5` to generate two scalable segments.
|
||||
let [first_half, second_half] = subcurve.split(0.5);
|
||||
let [first_half, second_half] = subcurve.split(TValue::Parametric(0.5));
|
||||
if first_half.is_scalable() && second_half.is_scalable() {
|
||||
result_beziers.push(first_half);
|
||||
result_beziers.push(second_half);
|
||||
@@ -191,14 +194,14 @@ impl Bezier {
|
||||
let mut t1 = 0.;
|
||||
let mut t2 = step_size;
|
||||
while t2 <= 1. + step_size {
|
||||
segment = subcurve.trim(t1, f64::min(t2, 1.));
|
||||
segment = subcurve.trim(TValue::Parametric(t1), TValue::Parametric(f64::min(t2, 1.)));
|
||||
if !segment.is_scalable() {
|
||||
t2 -= step_size;
|
||||
|
||||
// If the previous step does not exist, the start of the subcurve is irreducible.
|
||||
// Otherwise, add the valid segment from the previous step to the result.
|
||||
if f64::abs(t1 - t2) >= step_size {
|
||||
segment = subcurve.trim(t1, t2);
|
||||
segment = subcurve.trim(TValue::Parametric(t1), TValue::Parametric(t2));
|
||||
result_beziers.push(segment);
|
||||
result_t_values.push(t_subcurve_start + t2 * (t_subcurve_end - t_subcurve_start));
|
||||
} else {
|
||||
@@ -210,7 +213,7 @@ impl Bezier {
|
||||
}
|
||||
// Collect final remainder of the curve.
|
||||
if t1 < 1. {
|
||||
segment = subcurve.trim(t1, 1.);
|
||||
segment = subcurve.trim(TValue::Parametric(t1), TValue::Parametric(1.));
|
||||
if segment.is_scalable() {
|
||||
result_beziers.push(segment);
|
||||
result_t_values.push(t_subcurve_end);
|
||||
@@ -236,8 +239,8 @@ impl Bezier {
|
||||
fn scale(&self, distance: f64) -> Bezier {
|
||||
assert!(self.is_scalable(), "The curve provided to scale is not scalable. Reduce the curve first.");
|
||||
|
||||
let normal_start = self.normal(0.);
|
||||
let normal_end = self.normal(1.);
|
||||
let normal_start = self.normal(TValue::Parametric(0.));
|
||||
let normal_end = self.normal(TValue::Parametric(1.));
|
||||
|
||||
// If normal unit vectors are equal, then the lines are parallel
|
||||
if normal_start.abs_diff_eq(normal_end, MAX_ABSOLUTE_DIFFERENCE) {
|
||||
@@ -263,30 +266,30 @@ impl Bezier {
|
||||
pub fn graduated_scale(&self, start_distance: f64, end_distance: f64) -> Bezier {
|
||||
assert!(self.is_scalable(), "The curve provided to scale is not scalable. Reduce the curve first.");
|
||||
|
||||
let normal_start = self.normal(0.);
|
||||
let normal_end = self.normal(1.);
|
||||
let normal_start = self.normal(TValue::Parametric(0.));
|
||||
let normal_end = self.normal(TValue::Parametric(1.));
|
||||
|
||||
// If normal unit vectors are equal, then the lines are parallel
|
||||
if normal_start.abs_diff_eq(normal_end, MAX_ABSOLUTE_DIFFERENCE) {
|
||||
let transformed_start = utils::scale_point_from_direction_vector(self.start, self.normal(0.), false, start_distance);
|
||||
let transformed_end = utils::scale_point_from_direction_vector(self.end, self.normal(1.), false, end_distance);
|
||||
let transformed_start = utils::scale_point_from_direction_vector(self.start, self.normal(TValue::Parametric(0.)), false, start_distance);
|
||||
let transformed_end = utils::scale_point_from_direction_vector(self.end, self.normal(TValue::Parametric(1.)), false, end_distance);
|
||||
|
||||
return match self.handles {
|
||||
BezierHandles::Linear => Bezier::from_linear_dvec2(transformed_start, transformed_end),
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let handle_closest_t = self.project(handle, ProjectionOptions::default());
|
||||
let handle_scale_distance = (1. - handle_closest_t) * start_distance + handle_closest_t * end_distance;
|
||||
let transformed_handle = utils::scale_point_from_direction_vector(handle, self.normal(handle_closest_t), false, handle_scale_distance);
|
||||
let transformed_handle = utils::scale_point_from_direction_vector(handle, self.normal(TValue::Parametric(handle_closest_t)), false, handle_scale_distance);
|
||||
Bezier::from_quadratic_dvec2(transformed_start, transformed_handle, transformed_end)
|
||||
}
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let handle_start_closest_t = self.project(handle_start, ProjectionOptions::default());
|
||||
let handle_start_scale_distance = (1. - handle_start_closest_t) * start_distance + handle_start_closest_t * end_distance;
|
||||
let transformed_handle_start = utils::scale_point_from_direction_vector(handle_start, self.normal(handle_start_closest_t), false, handle_start_scale_distance);
|
||||
let transformed_handle_start = utils::scale_point_from_direction_vector(handle_start, self.normal(TValue::Parametric(handle_start_closest_t)), false, handle_start_scale_distance);
|
||||
|
||||
let handle_end_closest_t = self.project(handle_start, ProjectionOptions::default());
|
||||
let handle_end_scale_distance = (1. - handle_end_closest_t) * start_distance + handle_end_closest_t * end_distance;
|
||||
let transformed_handle_end = utils::scale_point_from_direction_vector(handle_end, self.normal(handle_end_closest_t), false, handle_end_scale_distance);
|
||||
let transformed_handle_end = utils::scale_point_from_direction_vector(handle_end, self.normal(TValue::Parametric(handle_end_closest_t)), false, handle_end_scale_distance);
|
||||
Bezier::from_cubic_dvec2(transformed_start, transformed_handle_start, transformed_handle_end, transformed_end)
|
||||
}
|
||||
};
|
||||
@@ -399,7 +402,7 @@ impl Bezier {
|
||||
match maximize_arcs {
|
||||
ArcStrategy::Automatic => {
|
||||
let (auto_arcs, final_low_t) = self.approximate_curve_with_arcs(0., 1., error, max_iterations, true);
|
||||
let arc_approximations = self.split(final_low_t)[1].arcs(ArcsOptions {
|
||||
let arc_approximations = self.split(TValue::Parametric(final_low_t))[1].arcs(ArcsOptions {
|
||||
strategy: ArcStrategy::FavorCorrectness,
|
||||
error,
|
||||
max_iterations,
|
||||
@@ -444,9 +447,9 @@ impl Bezier {
|
||||
// Inner loop to find the next maximal segment of the curve that can be approximated with a circular arc
|
||||
while iterations <= max_iterations {
|
||||
iterations += 1;
|
||||
let p1 = self.evaluate(ComputeType::Parametric(low));
|
||||
let p2 = self.evaluate(ComputeType::Parametric(middle));
|
||||
let p3 = self.evaluate(ComputeType::Parametric(high));
|
||||
let p1 = self.evaluate(TValue::Parametric(low));
|
||||
let p2 = self.evaluate(TValue::Parametric(middle));
|
||||
let p3 = self.evaluate(TValue::Parametric(high));
|
||||
|
||||
let wrapped_center = utils::compute_circle_center_from_points(p1, p2, p3);
|
||||
// If the segment is linear, move on to next segment
|
||||
@@ -486,8 +489,8 @@ impl Bezier {
|
||||
};
|
||||
|
||||
// Use points in between low, middle, and high to evaluate how well the arc approximates the curve
|
||||
let e1 = self.evaluate(ComputeType::Parametric((low + middle) / 2.));
|
||||
let e2 = self.evaluate(ComputeType::Parametric((middle + high) / 2.));
|
||||
let e1 = self.evaluate(TValue::Parametric((low + middle) / 2.));
|
||||
let e2 = self.evaluate(TValue::Parametric((middle + high) / 2.));
|
||||
|
||||
// Iterate until we find the largest good approximation such that the next iteration is not a good approximation with an arc
|
||||
if utils::f64_compare(radius, e1.distance(center), error) && utils::f64_compare(radius, e2.distance(center), error) {
|
||||
@@ -537,7 +540,7 @@ impl Bezier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::ComputeType;
|
||||
use crate::utils::TValue;
|
||||
|
||||
use super::compare::{compare_arcs, compare_vector_of_beziers};
|
||||
use super::*;
|
||||
@@ -545,37 +548,37 @@ mod tests {
|
||||
#[test]
|
||||
fn test_split() {
|
||||
let line = Bezier::from_linear_coordinates(25., 25., 75., 75.);
|
||||
let [part1, part2] = line.split(0.5);
|
||||
let [part1, part2] = line.split(TValue::Parametric(0.5));
|
||||
|
||||
assert_eq!(part1.start(), line.start());
|
||||
assert_eq!(part1.end(), line.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part1.evaluate(ComputeType::Parametric(0.5)), line.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(part1.end(), line.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part1.evaluate(TValue::Parametric(0.5)), line.evaluate(TValue::Parametric(0.25)));
|
||||
|
||||
assert_eq!(part2.start(), line.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part2.start(), line.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part2.end(), line.end());
|
||||
assert_eq!(part2.evaluate(ComputeType::Parametric(0.5)), line.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(part2.evaluate(TValue::Parametric(0.5)), line.evaluate(TValue::Parametric(0.75)));
|
||||
|
||||
let quad_bezier = Bezier::from_quadratic_coordinates(10., 10., 50., 50., 90., 10.);
|
||||
let [part3, part4] = quad_bezier.split(0.5);
|
||||
let [part3, part4] = quad_bezier.split(TValue::Parametric(0.5));
|
||||
|
||||
assert_eq!(part3.start(), quad_bezier.start());
|
||||
assert_eq!(part3.end(), quad_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part3.evaluate(ComputeType::Parametric(0.5)), quad_bezier.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(part3.end(), quad_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part3.evaluate(TValue::Parametric(0.5)), quad_bezier.evaluate(TValue::Parametric(0.25)));
|
||||
|
||||
assert_eq!(part4.start(), quad_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part4.start(), quad_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part4.end(), quad_bezier.end());
|
||||
assert_eq!(part4.evaluate(ComputeType::Parametric(0.5)), quad_bezier.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(part4.evaluate(TValue::Parametric(0.5)), quad_bezier.evaluate(TValue::Parametric(0.75)));
|
||||
|
||||
let cubic_bezier = Bezier::from_cubic_coordinates(10., 10., 50., 50., 90., 10., 40., 50.);
|
||||
let [part5, part6] = cubic_bezier.split(0.5);
|
||||
let [part5, part6] = cubic_bezier.split(TValue::Parametric(0.5));
|
||||
|
||||
assert_eq!(part5.start(), cubic_bezier.start());
|
||||
assert_eq!(part5.end(), cubic_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part5.evaluate(ComputeType::Parametric(0.5)), cubic_bezier.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(part5.end(), cubic_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part5.evaluate(TValue::Parametric(0.5)), cubic_bezier.evaluate(TValue::Parametric(0.25)));
|
||||
|
||||
assert_eq!(part6.start(), cubic_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(part6.start(), cubic_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
assert_eq!(part6.end(), cubic_bezier.end());
|
||||
assert_eq!(part6.evaluate(ComputeType::Parametric(0.5)), cubic_bezier.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(part6.evaluate(TValue::Parametric(0.5)), cubic_bezier.evaluate(TValue::Parametric(0.75)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -586,24 +589,24 @@ mod tests {
|
||||
let bezier_quadratic = Bezier::from_quadratic_dvec2(start, DVec2::new(140., 30.), end);
|
||||
|
||||
// Test splitting a quadratic bezier at the startpoint
|
||||
let [point_bezier1, remainder1] = bezier_quadratic.split(0.);
|
||||
let [point_bezier1, remainder1] = bezier_quadratic.split(TValue::Parametric(0.));
|
||||
assert_eq!(point_bezier1, Bezier::from_quadratic_dvec2(start, start, start));
|
||||
assert!(remainder1.abs_diff_eq(&bezier_quadratic, MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
// Test splitting a quadratic bezier at the endpoint
|
||||
let [remainder2, point_bezier2] = bezier_quadratic.split(1.);
|
||||
let [remainder2, point_bezier2] = bezier_quadratic.split(TValue::Parametric(1.));
|
||||
assert_eq!(point_bezier2, Bezier::from_quadratic_dvec2(end, end, end));
|
||||
assert!(remainder2.abs_diff_eq(&bezier_quadratic, MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
let bezier_cubic = Bezier::from_cubic_dvec2(start, DVec2::new(60., 140.), DVec2::new(150., 30.), end);
|
||||
|
||||
// Test splitting a cubic bezier at the startpoint
|
||||
let [point_bezier3, remainder3] = bezier_cubic.split(0.);
|
||||
let [point_bezier3, remainder3] = bezier_cubic.split(TValue::Parametric(0.));
|
||||
assert_eq!(point_bezier3, Bezier::from_cubic_dvec2(start, start, start, start));
|
||||
assert!(remainder3.abs_diff_eq(&bezier_cubic, MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
// Test splitting a cubic bezier at the endpoint
|
||||
let [remainder4, point_bezier4] = bezier_cubic.split(1.);
|
||||
let [remainder4, point_bezier4] = bezier_cubic.split(TValue::Parametric(1.));
|
||||
assert_eq!(point_bezier4, Bezier::from_cubic_dvec2(end, end, end, end));
|
||||
assert!(remainder4.abs_diff_eq(&bezier_cubic, MAX_ABSOLUTE_DIFFERENCE));
|
||||
}
|
||||
@@ -611,39 +614,39 @@ mod tests {
|
||||
#[test]
|
||||
fn test_trim() {
|
||||
let line = Bezier::from_linear_coordinates(80., 80., 40., 40.);
|
||||
let trimmed1 = line.trim(0.25, 0.75);
|
||||
let trimmed1 = line.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
|
||||
assert_eq!(trimmed1.start(), line.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(trimmed1.end(), line.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(trimmed1.evaluate(ComputeType::Parametric(0.5)), line.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(trimmed1.start(), line.evaluate(TValue::Parametric(0.25)));
|
||||
assert_eq!(trimmed1.end(), line.evaluate(TValue::Parametric(0.75)));
|
||||
assert_eq!(trimmed1.evaluate(TValue::Parametric(0.5)), line.evaluate(TValue::Parametric(0.5)));
|
||||
|
||||
let quadratic_bezier = Bezier::from_quadratic_coordinates(80., 80., 40., 40., 70., 70.);
|
||||
let trimmed2 = quadratic_bezier.trim(0.25, 0.75);
|
||||
let trimmed2 = quadratic_bezier.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
|
||||
assert_eq!(trimmed2.start(), quadratic_bezier.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(trimmed2.end(), quadratic_bezier.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(trimmed2.evaluate(ComputeType::Parametric(0.5)), quadratic_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(trimmed2.start(), quadratic_bezier.evaluate(TValue::Parametric(0.25)));
|
||||
assert_eq!(trimmed2.end(), quadratic_bezier.evaluate(TValue::Parametric(0.75)));
|
||||
assert_eq!(trimmed2.evaluate(TValue::Parametric(0.5)), quadratic_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
|
||||
let cubic_bezier = Bezier::from_cubic_coordinates(80., 80., 40., 40., 70., 70., 150., 150.);
|
||||
let trimmed3 = cubic_bezier.trim(0.25, 0.75);
|
||||
let trimmed3 = cubic_bezier.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
|
||||
assert_eq!(trimmed3.start(), cubic_bezier.evaluate(ComputeType::Parametric(0.25)));
|
||||
assert_eq!(trimmed3.end(), cubic_bezier.evaluate(ComputeType::Parametric(0.75)));
|
||||
assert_eq!(trimmed3.evaluate(ComputeType::Parametric(0.5)), cubic_bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(trimmed3.start(), cubic_bezier.evaluate(TValue::Parametric(0.25)));
|
||||
assert_eq!(trimmed3.end(), cubic_bezier.evaluate(TValue::Parametric(0.75)));
|
||||
assert_eq!(trimmed3.evaluate(TValue::Parametric(0.5)), cubic_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_t2_greater_than_t1() {
|
||||
// Test trimming quadratic curve when t2 > t1
|
||||
let bezier_quadratic = Bezier::from_quadratic_coordinates(30., 50., 140., 30., 160., 170.);
|
||||
let trim1 = bezier_quadratic.trim(0.25, 0.75);
|
||||
let trim2 = bezier_quadratic.trim(0.75, 0.25).reverse();
|
||||
let trim1 = bezier_quadratic.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
let trim2 = bezier_quadratic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25)).reverse();
|
||||
assert!(trim1.abs_diff_eq(&trim2, MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
// Test trimming cubic curve when t2 > t1
|
||||
let bezier_cubic = Bezier::from_cubic_coordinates(30., 30., 60., 140., 150., 30., 160., 160.);
|
||||
let trim3 = bezier_cubic.trim(0.25, 0.75);
|
||||
let trim4 = bezier_cubic.trim(0.75, 0.25).reverse();
|
||||
let trim3 = bezier_cubic.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
let trim4 = bezier_cubic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25)).reverse();
|
||||
assert!(trim3.abs_diff_eq(&trim4, MAX_ABSOLUTE_DIFFERENCE));
|
||||
}
|
||||
|
||||
@@ -704,7 +707,7 @@ mod tests {
|
||||
assert!(reduced_curves
|
||||
.iter()
|
||||
.zip(helper_t_values.windows(2))
|
||||
.all(|(curve, t_pair)| curve.abs_diff_eq(&bezier.trim(t_pair[0], t_pair[1]), MAX_ABSOLUTE_DIFFERENCE)))
|
||||
.all(|(curve, t_pair)| curve.abs_diff_eq(&bezier.trim(TValue::Parametric(t_pair[0]), TValue::Parametric(t_pair[1])), MAX_ABSOLUTE_DIFFERENCE)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -744,23 +747,23 @@ mod tests {
|
||||
|
||||
// Assert the first length-wise piece of the outline is 10 units from the line
|
||||
assert!(f64_compare(
|
||||
outline[0].evaluate(ComputeType::Parametric(0.25)).distance(line.evaluate(ComputeType::Parametric(0.25))),
|
||||
outline[0].evaluate(TValue::Parametric(0.25)).distance(line.evaluate(TValue::Parametric(0.25))),
|
||||
10.,
|
||||
MAX_ABSOLUTE_DIFFERENCE
|
||||
)); // f64
|
||||
|
||||
// Assert the first cap touches the line end point at the halfway point
|
||||
assert!(outline[1].evaluate(ComputeType::Parametric(0.5)).abs_diff_eq(line.end(), MAX_ABSOLUTE_DIFFERENCE));
|
||||
assert!(outline[1].evaluate(TValue::Parametric(0.5)).abs_diff_eq(line.end(), MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
// Assert the second length-wise piece of the outline is 10 units from the line
|
||||
assert!(f64_compare(
|
||||
outline[2].evaluate(ComputeType::Parametric(0.25)).distance(line.evaluate(ComputeType::Parametric(0.75))),
|
||||
outline[2].evaluate(TValue::Parametric(0.25)).distance(line.evaluate(TValue::Parametric(0.75))),
|
||||
10.,
|
||||
MAX_ABSOLUTE_DIFFERENCE
|
||||
)); // f64
|
||||
|
||||
// Assert the second cap touches the line start point at the halfway point
|
||||
assert!(outline[3].evaluate(ComputeType::Parametric(0.5)).abs_diff_eq(line.start(), MAX_ABSOLUTE_DIFFERENCE));
|
||||
assert!(outline[3].evaluate(TValue::Parametric(0.5)).abs_diff_eq(line.start(), MAX_ABSOLUTE_DIFFERENCE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -778,17 +781,17 @@ mod tests {
|
||||
|
||||
// Assert the scaled bezier is 30 units from the line
|
||||
assert!(f64_compare(
|
||||
scaled_bezier.evaluate(ComputeType::Parametric(0.)).distance(bezier.evaluate(ComputeType::Parametric(0.))),
|
||||
scaled_bezier.evaluate(TValue::Parametric(0.)).distance(bezier.evaluate(TValue::Parametric(0.))),
|
||||
30.,
|
||||
MAX_ABSOLUTE_DIFFERENCE
|
||||
));
|
||||
assert!(f64_compare(
|
||||
scaled_bezier.evaluate(ComputeType::Parametric(1.)).distance(bezier.evaluate(ComputeType::Parametric(1.))),
|
||||
scaled_bezier.evaluate(TValue::Parametric(1.)).distance(bezier.evaluate(TValue::Parametric(1.))),
|
||||
30.,
|
||||
MAX_ABSOLUTE_DIFFERENCE
|
||||
));
|
||||
assert!(f64_compare(
|
||||
scaled_bezier.evaluate(ComputeType::Parametric(0.5)).distance(bezier.evaluate(ComputeType::Parametric(0.5))),
|
||||
scaled_bezier.evaluate(TValue::Parametric(0.5)).distance(bezier.evaluate(TValue::Parametric(0.5))),
|
||||
30.,
|
||||
MAX_ABSOLUTE_DIFFERENCE
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user