mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 21:28:13 +08:00
* 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>
117 lines
3.6 KiB
Rust
117 lines
3.6 KiB
Rust
use super::*;
|
|
use crate::{ProjectionOptions, TValue};
|
|
use glam::DVec2;
|
|
|
|
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
|
impl Subpath {
|
|
/// 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`.
|
|
pub fn length(&self, num_subdivisions: Option<usize>) -> f64 {
|
|
self.iter().fold(0., |accumulator, bezier| accumulator + bezier.length(num_subdivisions))
|
|
}
|
|
|
|
/// Returns the segment index and `t` value that corresponds to the closest point on the curve to the provided point.
|
|
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
|
|
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> Option<(usize, f64)> {
|
|
if self.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
// TODO: Optimization opportunity: Filter out segments which are *definitely* not the closest to the given point
|
|
let (index, (_, project_t)) = self
|
|
.iter()
|
|
.map(|bezier| {
|
|
let project_t = bezier.project(point, options);
|
|
(bezier.evaluate(TValue::Parametric(project_t)).distance(point), project_t)
|
|
})
|
|
.enumerate()
|
|
.min_by(|(_, (distance1, _)), (_, (distance2, _))| distance1.total_cmp(distance2))
|
|
.unwrap_or((0, (0., 0.))); // If the Subpath contains only a single manipulator group, returns (0, 0.)
|
|
|
|
Some((index, project_t))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn length_quadratic() {
|
|
let start = DVec2::new(20., 30.);
|
|
let middle = DVec2::new(80., 90.);
|
|
let end = DVec2::new(60., 45.);
|
|
let handle1 = DVec2::new(75., 85.);
|
|
let handle2 = DVec2::new(40., 30.);
|
|
let handle3 = DVec2::new(10., 10.);
|
|
|
|
let bezier1 = Bezier::from_quadratic_dvec2(start, handle1, middle);
|
|
let bezier2 = Bezier::from_quadratic_dvec2(middle, handle2, end);
|
|
let bezier3 = Bezier::from_quadratic_dvec2(end, handle3, start);
|
|
|
|
let mut subpath = Subpath::new(
|
|
vec![
|
|
ManipulatorGroup {
|
|
anchor: start,
|
|
in_handle: None,
|
|
out_handle: Some(handle1),
|
|
},
|
|
ManipulatorGroup {
|
|
anchor: middle,
|
|
in_handle: None,
|
|
out_handle: Some(handle2),
|
|
},
|
|
ManipulatorGroup {
|
|
anchor: end,
|
|
in_handle: None,
|
|
out_handle: Some(handle3),
|
|
},
|
|
],
|
|
false,
|
|
);
|
|
assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None));
|
|
|
|
subpath.closed = true;
|
|
assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None) + bezier3.length(None));
|
|
}
|
|
|
|
#[test]
|
|
fn length_mixed() {
|
|
let start = DVec2::new(20., 30.);
|
|
let middle = DVec2::new(70., 70.);
|
|
let end = DVec2::new(60., 45.);
|
|
let handle1 = DVec2::new(75., 85.);
|
|
let handle2 = DVec2::new(40., 30.);
|
|
let handle3 = DVec2::new(10., 10.);
|
|
|
|
let linear_bezier = Bezier::from_linear_dvec2(start, middle);
|
|
let quadratic_bezier = Bezier::from_quadratic_dvec2(middle, handle1, end);
|
|
let cubic_bezier = Bezier::from_cubic_dvec2(end, handle2, handle3, start);
|
|
|
|
let mut subpath = Subpath::new(
|
|
vec![
|
|
ManipulatorGroup {
|
|
anchor: start,
|
|
in_handle: Some(handle3),
|
|
out_handle: None,
|
|
},
|
|
ManipulatorGroup {
|
|
anchor: middle,
|
|
in_handle: None,
|
|
out_handle: Some(handle1),
|
|
},
|
|
ManipulatorGroup {
|
|
anchor: end,
|
|
in_handle: None,
|
|
out_handle: Some(handle2),
|
|
},
|
|
],
|
|
false,
|
|
);
|
|
assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None));
|
|
|
|
subpath.closed = true;
|
|
assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None) + cubic_bezier.length(None));
|
|
}
|
|
}
|