mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-31 02:07:21 +00: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:
parent
f0ad4c91d3
commit
a64c856ec4
25 changed files with 456 additions and 433 deletions
|
@ -1,6 +1,6 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::ComputeType;
|
||||
use bezier_rs::TValue;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
|
@ -296,7 +296,7 @@ impl ShapeEditor {
|
|||
for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() {
|
||||
let bezier = bezier_id.internal;
|
||||
let t = bezier.project(layer_pos, projection_options);
|
||||
let layerspace = bezier.evaluate(ComputeType::Parametric(t));
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
@ -314,7 +314,7 @@ impl ShapeEditor {
|
|||
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) {
|
||||
let [first, second] = bezier_id.internal.split(t);
|
||||
let [first, second] = bezier_id.internal.split(TValue::Parametric(t));
|
||||
|
||||
// Adjust the first manipulator group's out handle
|
||||
let out_handle = Operation::SetManipulatorPoints {
|
||||
|
|
|
@ -203,7 +203,7 @@ impl Bezier {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::ComputeType;
|
||||
use crate::utils::TValue;
|
||||
|
||||
use super::compare::compare_points;
|
||||
use super::*;
|
||||
|
@ -215,13 +215,13 @@ mod tests {
|
|||
let p3 = DVec2::new(160., 170.);
|
||||
|
||||
let bezier1 = Bezier::quadratic_through_points(p1, p2, p3, None);
|
||||
assert!(compare_points(bezier1.evaluate(ComputeType::Parametric(0.5)), p2));
|
||||
assert!(compare_points(bezier1.evaluate(TValue::Parametric(0.5)), p2));
|
||||
|
||||
let bezier2 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.8));
|
||||
assert!(compare_points(bezier2.evaluate(ComputeType::Parametric(0.8)), p2));
|
||||
assert!(compare_points(bezier2.evaluate(TValue::Parametric(0.8)), p2));
|
||||
|
||||
let bezier3 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.));
|
||||
assert!(compare_points(bezier3.evaluate(ComputeType::Parametric(0.)), p2));
|
||||
assert!(compare_points(bezier3.evaluate(TValue::Parametric(0.)), p2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -231,12 +231,12 @@ mod tests {
|
|||
let p3 = DVec2::new(160., 160.);
|
||||
|
||||
let bezier1 = Bezier::cubic_through_points(p1, p2, p3, Some(0.3), Some(10.));
|
||||
assert!(compare_points(bezier1.evaluate(ComputeType::Parametric(0.3)), p2));
|
||||
assert!(compare_points(bezier1.evaluate(TValue::Parametric(0.3)), p2));
|
||||
|
||||
let bezier2 = Bezier::cubic_through_points(p1, p2, p3, Some(0.8), Some(91.7));
|
||||
assert!(compare_points(bezier2.evaluate(ComputeType::Parametric(0.8)), p2));
|
||||
assert!(compare_points(bezier2.evaluate(TValue::Parametric(0.8)), p2));
|
||||
|
||||
let bezier3 = Bezier::cubic_through_points(p1, p2, p3, Some(0.), Some(91.7));
|
||||
assert!(compare_points(bezier3.evaluate(ComputeType::Parametric(0.)), p2));
|
||||
assert!(compare_points(bezier3.evaluate(TValue::Parametric(0.)), p2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,49 @@
|
|||
use crate::utils::{f64_compare, ComputeType};
|
||||
use crate::utils::{f64_compare, TValue};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Functionality relating to looking up properties of the `Bezier` or points along the `Bezier`.
|
||||
impl Bezier {
|
||||
/// Convert a euclidean distance ratio along the `Bezier` curve to a parametric `t`-value.
|
||||
pub fn euclidean_to_parametric(&self, ratio: f64, error: f64) -> f64 {
|
||||
let mut low = 0.;
|
||||
let mut mid = 0.;
|
||||
let mut high = 1.;
|
||||
let total_length = self.length(None);
|
||||
|
||||
while low < high {
|
||||
mid = (low + high) / 2.;
|
||||
let test_ratio = self.trim(TValue::Parametric(0.), TValue::Parametric(mid)).length(None) / total_length;
|
||||
if f64_compare(test_ratio, ratio, error) {
|
||||
break;
|
||||
} else if test_ratio < ratio {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
mid
|
||||
}
|
||||
|
||||
/// Convert a [TValue] to a parametric `t`-value.
|
||||
pub(crate) fn t_value_to_parametric(&self, t: TValue) -> f64 {
|
||||
match t {
|
||||
TValue::Parametric(t) => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
t
|
||||
}
|
||||
TValue::Euclidean(t) => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
self.euclidean_to_parametric(t, DEFAULT_EUCLIDEAN_ERROR_BOUND)
|
||||
}
|
||||
TValue::EuclideanWithinError { t, error } => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
self.euclidean_to_parametric(t, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the point on the curve based on the `t`-value provided.
|
||||
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 {
|
||||
// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>.
|
||||
|
@ -23,48 +63,11 @@ impl Bezier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculate the point along the curve that is a factor of `d` away from the start.
|
||||
pub(crate) fn unrestricted_euclidean_evaluate(&self, d: f64, error: f64) -> DVec2 {
|
||||
if let BezierHandles::Linear = self.handles {
|
||||
return self.unrestricted_parametric_evaluate(d);
|
||||
}
|
||||
|
||||
let mut low = 0.;
|
||||
let mut mid = 0.;
|
||||
let mut high = 1.;
|
||||
let total_length = self.length(None);
|
||||
|
||||
while low < high {
|
||||
mid = (low + high) / 2.;
|
||||
let test_d = self.trim(0., mid).length(None) / total_length;
|
||||
if f64_compare(test_d, d, error) {
|
||||
break;
|
||||
} else if test_d < d {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
self.unrestricted_parametric_evaluate(mid)
|
||||
}
|
||||
|
||||
/// Calculate the point on the curve based on the `t`-value provided.
|
||||
/// Calculate the coordinates of the point `t` along the curve.
|
||||
/// Expects `t` to be within the inclusive range `[0, 1]`.
|
||||
pub fn evaluate(&self, t: ComputeType) -> DVec2 {
|
||||
match t {
|
||||
ComputeType::Parametric(t) => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
self.unrestricted_parametric_evaluate(t)
|
||||
}
|
||||
ComputeType::Euclidean(t) => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
self.unrestricted_euclidean_evaluate(t, 0.0001)
|
||||
}
|
||||
ComputeType::EuclideanWithinError { t, epsilon } => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
self.unrestricted_euclidean_evaluate(t, epsilon)
|
||||
}
|
||||
}
|
||||
pub fn evaluate(&self, t: TValue) -> DVec2 {
|
||||
let t = self.t_value_to_parametric(t);
|
||||
self.unrestricted_parametric_evaluate(t)
|
||||
}
|
||||
|
||||
/// Return a selection of equidistant points on the bezier curve.
|
||||
|
@ -75,7 +78,7 @@ impl Bezier {
|
|||
let mut steps_array = Vec::with_capacity(steps_unwrapped + 1);
|
||||
|
||||
for t in 0..steps_unwrapped + 1 {
|
||||
steps_array.push(self.evaluate(ComputeType::Parametric(f64::from(t as i32) * ratio)))
|
||||
steps_array.push(self.evaluate(TValue::Parametric(f64::from(t as i32) * ratio)))
|
||||
}
|
||||
|
||||
steps_array
|
||||
|
@ -107,7 +110,7 @@ impl Bezier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the `t` value that corresponds to the closest point on the curve to the provided point.
|
||||
/// Returns the parametric `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) -> f64 {
|
||||
let ProjectionOptions {
|
||||
|
@ -162,7 +165,7 @@ impl Bezier {
|
|||
if step_index == 0 {
|
||||
distance = *table_distance;
|
||||
} else {
|
||||
distance = point.distance(self.evaluate(ComputeType::Parametric(iterator_t)));
|
||||
distance = point.distance(self.evaluate(TValue::Parametric(iterator_t)));
|
||||
*table_distance = distance;
|
||||
}
|
||||
if distance < new_minimum_distance {
|
||||
|
@ -212,17 +215,17 @@ mod tests {
|
|||
let p4 = DVec2::new(30., 21.);
|
||||
|
||||
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||
assert_eq!(bezier1.evaluate(ComputeType::Parametric(0.5)), DVec2::new(12.5, 6.25));
|
||||
assert_eq!(bezier1.evaluate(TValue::Parametric(0.5)), DVec2::new(12.5, 6.25));
|
||||
|
||||
let bezier2 = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
||||
assert_eq!(bezier2.evaluate(ComputeType::Parametric(0.5)), DVec2::new(16.5, 9.625));
|
||||
assert_eq!(bezier2.evaluate(TValue::Parametric(0.5)), DVec2::new(16.5, 9.625));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_lookup_table() {
|
||||
let bezier1 = Bezier::from_quadratic_coordinates(10., 10., 30., 30., 50., 10.);
|
||||
let lookup_table1 = bezier1.compute_lookup_table(Some(2));
|
||||
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(ComputeType::Parametric(0.5)), bezier1.end()]);
|
||||
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(TValue::Parametric(0.5)), bezier1.end()]);
|
||||
|
||||
let bezier2 = Bezier::from_cubic_coordinates(10., 10., 30., 30., 70., 70., 90., 10.);
|
||||
let lookup_table2 = bezier2.compute_lookup_table(Some(4));
|
||||
|
@ -230,9 +233,9 @@ mod tests {
|
|||
lookup_table2,
|
||||
vec![
|
||||
bezier2.start(),
|
||||
bezier2.evaluate(ComputeType::Parametric(0.25)),
|
||||
bezier2.evaluate(ComputeType::Parametric(0.50)),
|
||||
bezier2.evaluate(ComputeType::Parametric(0.75)),
|
||||
bezier2.evaluate(TValue::Parametric(0.25)),
|
||||
bezier2.evaluate(TValue::Parametric(0.50)),
|
||||
bezier2.evaluate(TValue::Parametric(0.75)),
|
||||
bezier2.end()
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use super::*;
|
||||
use crate::utils::ComputeType;
|
||||
use crate::utils::TValue;
|
||||
|
||||
use glam::DMat2;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Functionality that solve for various curve information such as derivative, tangent, intersect, etc.
|
||||
impl Bezier {
|
||||
/// Returns a list of lists of points representing the De Casteljau points for all iterations at the point corresponding to `t` using De Casteljau's algorithm.
|
||||
/// Returns a list of lists of points representing the De Casteljau points for all iterations at the point `t` along the curve using De Casteljau's algorithm.
|
||||
/// The `i`th element of the list represents the set of points in the `i`th iteration.
|
||||
/// More information on the algorithm can be found in the [De Casteljau section](https://pomax.github.io/bezierinfo/#decasteljau) in Pomax's primer.
|
||||
pub fn de_casteljau_points(&self, t: f64) -> Vec<Vec<DVec2>> {
|
||||
pub fn de_casteljau_points(&self, t: TValue) -> Vec<Vec<DVec2>> {
|
||||
let t = self.t_value_to_parametric(t);
|
||||
let bezier_points = match self.handles {
|
||||
BezierHandles::Linear => vec![self.start, self.end],
|
||||
BezierHandles::Quadratic { handle } => vec![self.start, handle, self.end],
|
||||
|
@ -30,7 +31,7 @@ impl Bezier {
|
|||
de_casteljau_points
|
||||
}
|
||||
|
||||
/// Returns a Bezier representing the derivative of the original curve.
|
||||
/// Returns a [Bezier] representing the derivative of the original curve.
|
||||
/// - This function returns `None` for a linear segment.
|
||||
pub fn derivative(&self) -> Option<Bezier> {
|
||||
match self.handles {
|
||||
|
@ -49,27 +50,29 @@ impl Bezier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a normalized unit vector representing the tangent at the point designated by `t` on the curve.
|
||||
pub fn tangent(&self, t: f64) -> DVec2 {
|
||||
/// Returns a normalized unit vector representing the tangent at the point `t` along the curve.
|
||||
pub fn tangent(&self, t: TValue) -> DVec2 {
|
||||
let t = self.t_value_to_parametric(t);
|
||||
match self.handles {
|
||||
BezierHandles::Linear => self.end - self.start,
|
||||
_ => self.derivative().unwrap().evaluate(ComputeType::Parametric(t)),
|
||||
_ => self.derivative().unwrap().evaluate(TValue::Parametric(t)),
|
||||
}
|
||||
.normalize()
|
||||
}
|
||||
|
||||
/// Returns a normalized unit vector representing the direction of the normal at the point designated by `t` on the curve.
|
||||
pub fn normal(&self, t: f64) -> DVec2 {
|
||||
/// Returns a normalized unit vector representing the direction of the normal at the point `t` along the curve.
|
||||
pub fn normal(&self, t: TValue) -> DVec2 {
|
||||
self.tangent(t).perp()
|
||||
}
|
||||
|
||||
/// Returns the curvature, a scalar value for the derivative at the given `t`-value along the curve.
|
||||
/// Returns the curvature, a scalar value for the derivative at the point `t` along the curve.
|
||||
/// Curvature is 1 over the radius of a circle with an equivalent derivative.
|
||||
pub fn curvature(&self, t: f64) -> f64 {
|
||||
pub fn curvature(&self, t: TValue) -> f64 {
|
||||
let t = self.t_value_to_parametric(t);
|
||||
let (d, dd) = match &self.derivative() {
|
||||
Some(first_derivative) => match first_derivative.derivative() {
|
||||
Some(second_derivative) => (first_derivative.evaluate(ComputeType::Parametric(t)), second_derivative.evaluate(ComputeType::Parametric(t))),
|
||||
None => (first_derivative.evaluate(ComputeType::Parametric(t)), first_derivative.end - first_derivative.start),
|
||||
Some(second_derivative) => (first_derivative.evaluate(TValue::Parametric(t)), second_derivative.evaluate(TValue::Parametric(t))),
|
||||
None => (first_derivative.evaluate(TValue::Parametric(t)), first_derivative.end - first_derivative.start),
|
||||
},
|
||||
None => (self.end - self.start, DVec2::new(0., 0.)),
|
||||
};
|
||||
|
@ -129,7 +132,7 @@ impl Bezier {
|
|||
let extrema = self.local_extrema();
|
||||
for t_values in extrema {
|
||||
for t in t_values {
|
||||
let point = self.evaluate(ComputeType::Parametric(t));
|
||||
let point = self.evaluate(TValue::Parametric(t));
|
||||
// Update bounding box if new min/max is found.
|
||||
endpoints_min = endpoints_min.min(point);
|
||||
endpoints_max = endpoints_max.max(point);
|
||||
|
@ -178,7 +181,7 @@ impl Bezier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns list of `t`-values representing the inflection points of the curve.
|
||||
/// Returns list of parametric `t`-values representing the inflection points of the curve.
|
||||
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
|
||||
pub fn inflections(&self) -> Vec<f64> {
|
||||
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
|
||||
|
@ -213,8 +216,8 @@ impl Bezier {
|
|||
}
|
||||
|
||||
// Split curves in half and repeat with the combinations of the two halves of each curve
|
||||
let [split_1_a, split_1_b] = self.split(0.5);
|
||||
let [split_2_a, split_2_b] = other.split(0.5);
|
||||
let [split_1_a, split_1_b] = self.split(TValue::Parametric(0.5));
|
||||
let [split_2_a, split_2_b] = other.split(TValue::Parametric(0.5));
|
||||
|
||||
[
|
||||
split_1_a.intersections_between_subcurves(self_start_t..self_mid_t, &split_2_a, other_start_t..other_mid_t, error),
|
||||
|
@ -229,7 +232,7 @@ impl Bezier {
|
|||
}
|
||||
|
||||
// TODO: Use an `impl Iterator` return type instead of a `Vec`
|
||||
/// Returns a list of filtered `t` values that correspond to intersection points between the current bezier curve and the provided one
|
||||
/// Returns a list of filtered parametric `t` values that correspond to intersection points between the current bezier curve and the provided one
|
||||
/// such that the difference between adjacent `t` values in sorted order is greater than some minimum seperation value. If the difference
|
||||
/// between 2 adjacent `t` values is lesss than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.
|
||||
/// The returned `t` values are with respect to the current bezier, not the provided parameter.
|
||||
|
@ -242,8 +245,6 @@ impl Bezier {
|
|||
let mut intersection_t_values = self.unfiltered_intersections(other, error);
|
||||
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
// println!("<<<<< intersection_t_values :: {:?}", intersection_t_values);
|
||||
|
||||
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
||||
if !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_seperation.unwrap_or(MIN_SEPERATION_VALUE) {
|
||||
accumulator.pop();
|
||||
|
@ -334,7 +335,7 @@ impl Bezier {
|
|||
}
|
||||
|
||||
// TODO: Use an `impl Iterator` return type instead of a `Vec`
|
||||
/// Returns a list of `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
pub fn self_intersections(&self, error: Option<f64>) -> Vec<[f64; 2]> {
|
||||
if self.handles == BezierHandles::Linear || matches!(self.handles, BezierHandles::Quadratic { .. }) {
|
||||
|
@ -362,7 +363,7 @@ impl Bezier {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a list of `t` values that correspond to the intersection points between the curve and a rectangle defined by opposite corners.
|
||||
/// Returns a list of parametric `t` values that correspond to the intersection points between the curve and a rectangle defined by opposite corners.
|
||||
pub fn rectangle_intersections(&self, corner1: DVec2, corner2: DVec2) -> Vec<f64> {
|
||||
[
|
||||
Bezier::from_linear_coordinates(corner1.x, corner1.y, corner2.x, corner1.y),
|
||||
|
@ -384,7 +385,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_de_casteljau_points() {
|
||||
let bezier = Bezier::from_cubic_coordinates(0., 0., 0., 100., 100., 100., 100., 0.);
|
||||
let de_casteljau_points = bezier.de_casteljau_points(0.5);
|
||||
let de_casteljau_points = bezier.de_casteljau_points(TValue::Parametric(0.5));
|
||||
let expected_de_casteljau_points = vec![
|
||||
vec![DVec2::new(0., 0.), DVec2::new(0., 100.), DVec2::new(100., 100.), DVec2::new(100., 0.)],
|
||||
vec![DVec2::new(0., 50.), DVec2::new(50., 100.), DVec2::new(100., 50.)],
|
||||
|
@ -393,7 +394,7 @@ mod tests {
|
|||
];
|
||||
assert_eq!(&de_casteljau_points, &expected_de_casteljau_points);
|
||||
|
||||
assert_eq!(expected_de_casteljau_points[3][0], bezier.evaluate(ComputeType::Parametric(0.5)));
|
||||
assert_eq!(expected_de_casteljau_points[3][0], bezier.evaluate(TValue::Parametric(0.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -433,16 +434,16 @@ mod tests {
|
|||
|
||||
let linear = Bezier::from_linear_dvec2(p1, p2);
|
||||
let unit_slope = DVec2::new(30., 20.).normalize();
|
||||
assert_eq!(linear.tangent(0.), unit_slope);
|
||||
assert_eq!(linear.tangent(1.), unit_slope);
|
||||
assert_eq!(linear.tangent(TValue::Parametric(0.)), unit_slope);
|
||||
assert_eq!(linear.tangent(TValue::Parametric(1.)), unit_slope);
|
||||
|
||||
let quadratic = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||
assert_eq!(quadratic.tangent(0.), DVec2::new(60., 40.).normalize());
|
||||
assert_eq!(quadratic.tangent(1.), DVec2::new(40., 60.).normalize());
|
||||
assert_eq!(quadratic.tangent(TValue::Parametric(0.)), DVec2::new(60., 40.).normalize());
|
||||
assert_eq!(quadratic.tangent(TValue::Parametric(1.)), DVec2::new(40., 60.).normalize());
|
||||
|
||||
let cubic = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
||||
assert_eq!(cubic.tangent(0.), DVec2::new(90., 60.).normalize());
|
||||
assert_eq!(cubic.tangent(1.), DVec2::new(30., 120.).normalize());
|
||||
assert_eq!(cubic.tangent(TValue::Parametric(0.)), DVec2::new(90., 60.).normalize());
|
||||
assert_eq!(cubic.tangent(TValue::Parametric(1.)), DVec2::new(30., 120.).normalize());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -455,16 +456,16 @@ mod tests {
|
|||
|
||||
let linear = Bezier::from_linear_dvec2(p1, p2);
|
||||
let unit_slope = DVec2::new(-20., 30.).normalize();
|
||||
assert_eq!(linear.normal(0.), unit_slope);
|
||||
assert_eq!(linear.normal(1.), unit_slope);
|
||||
assert_eq!(linear.normal(TValue::Parametric(0.)), unit_slope);
|
||||
assert_eq!(linear.normal(TValue::Parametric(1.)), unit_slope);
|
||||
|
||||
let quadratic = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||
assert_eq!(quadratic.normal(0.), DVec2::new(-40., 60.).normalize());
|
||||
assert_eq!(quadratic.normal(1.), DVec2::new(-60., 40.).normalize());
|
||||
assert_eq!(quadratic.normal(TValue::Parametric(0.)), DVec2::new(-40., 60.).normalize());
|
||||
assert_eq!(quadratic.normal(TValue::Parametric(1.)), DVec2::new(-60., 40.).normalize());
|
||||
|
||||
let cubic = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
||||
assert_eq!(cubic.normal(0.), DVec2::new(-60., 90.).normalize());
|
||||
assert_eq!(cubic.normal(1.), DVec2::new(-120., 30.).normalize());
|
||||
assert_eq!(cubic.normal(TValue::Parametric(0.)), DVec2::new(-60., 90.).normalize());
|
||||
assert_eq!(cubic.normal(TValue::Parametric(1.)), DVec2::new(-120., 30.).normalize());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -475,24 +476,24 @@ mod tests {
|
|||
let p4 = DVec2::new(50., 10.);
|
||||
|
||||
let linear = Bezier::from_linear_dvec2(p1, p2);
|
||||
assert_eq!(linear.curvature(0.), 0.);
|
||||
assert_eq!(linear.curvature(0.5), 0.);
|
||||
assert_eq!(linear.curvature(1.), 0.);
|
||||
assert_eq!(linear.curvature(TValue::Parametric(0.)), 0.);
|
||||
assert_eq!(linear.curvature(TValue::Parametric(0.5)), 0.);
|
||||
assert_eq!(linear.curvature(TValue::Parametric(1.)), 0.);
|
||||
|
||||
let quadratic = Bezier::from_quadratic_dvec2(p1, p2, p3);
|
||||
assert!(compare_f64s(quadratic.curvature(0.), 0.0125));
|
||||
assert!(compare_f64s(quadratic.curvature(0.5), 0.035355));
|
||||
assert!(compare_f64s(quadratic.curvature(1.), 0.0125));
|
||||
assert!(compare_f64s(quadratic.curvature(TValue::Parametric(0.)), 0.0125));
|
||||
assert!(compare_f64s(quadratic.curvature(TValue::Parametric(0.5)), 0.035355));
|
||||
assert!(compare_f64s(quadratic.curvature(TValue::Parametric(1.)), 0.0125));
|
||||
|
||||
let cubic = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
|
||||
assert!(compare_f64s(cubic.curvature(0.), 0.016667));
|
||||
assert!(compare_f64s(cubic.curvature(0.5), 0.));
|
||||
assert!(compare_f64s(cubic.curvature(1.), 0.));
|
||||
assert!(compare_f64s(cubic.curvature(TValue::Parametric(0.)), 0.016667));
|
||||
assert!(compare_f64s(cubic.curvature(TValue::Parametric(0.5)), 0.));
|
||||
assert!(compare_f64s(cubic.curvature(TValue::Parametric(1.)), 0.));
|
||||
|
||||
// The curvature at an inflection point is zero
|
||||
let inflection_curve = Bezier::from_cubic_coordinates(30., 30., 30., 150., 150., 30., 150., 150.);
|
||||
let inflections = inflection_curve.inflections();
|
||||
assert_eq!(inflection_curve.curvature(inflections[0]), 0.);
|
||||
assert_eq!(inflection_curve.curvature(TValue::Parametric(inflections[0])), 0.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -609,12 +610,12 @@ mod tests {
|
|||
let line1 = Bezier::from_linear_coordinates(20., 60., 70., 60.);
|
||||
let intersections1 = bezier.intersections(&line1, None, None);
|
||||
assert!(intersections1.len() == 1);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections1[0])), DVec2::new(30., 60.)));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections1[0])), DVec2::new(30., 60.)));
|
||||
|
||||
// Intersection in the middle of curve
|
||||
let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.);
|
||||
let intersections2 = bezier.intersections(&line2, None, None);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections2[0])), DVec2::new(96., 96.)));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections2[0])), DVec2::new(96., 96.)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -628,12 +629,12 @@ mod tests {
|
|||
let line1 = Bezier::from_linear_coordinates(20., 50., 40., 50.);
|
||||
let intersections1 = bezier.intersections(&line1, None, None);
|
||||
assert!(intersections1.len() == 1);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections1[0])), p1));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections1[0])), p1));
|
||||
|
||||
// Intersection in the middle of curve
|
||||
let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.);
|
||||
let intersections2 = bezier.intersections(&line2, None, None);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections2[0])), DVec2::new(47.77355, 47.77354)));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections2[0])), DVec2::new(47.77355, 47.77354)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -648,14 +649,14 @@ mod tests {
|
|||
let line1 = Bezier::from_linear_coordinates(20., 30., 40., 30.);
|
||||
let intersections1 = bezier.intersections(&line1, None, None);
|
||||
assert!(intersections1.len() == 1);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections1[0])), p1));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections1[0])), p1));
|
||||
|
||||
// Intersection at edge and in middle of curve, Discriminant < 0
|
||||
let line2 = Bezier::from_linear_coordinates(150., 150., 30., 30.);
|
||||
let intersections2 = bezier.intersections(&line2, None, None);
|
||||
assert!(intersections2.len() == 2);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections2[0])), p1));
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections2[1])), DVec2::new(85.84, 85.84)));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections2[0])), p1));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections2[1])), DVec2::new(85.84, 85.84)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -672,7 +673,7 @@ mod tests {
|
|||
let intersections = bezier.intersections(&line, None, None);
|
||||
|
||||
assert_eq!(intersections.len(), 1);
|
||||
assert!(compare_points(bezier.evaluate(ComputeType::Parametric(intersections[0])), p4));
|
||||
assert!(compare_points(bezier.evaluate(TValue::Parametric(intersections[0])), p4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -699,8 +700,8 @@ mod tests {
|
|||
let intersections1 = bezier1.intersections(&bezier2, None, None);
|
||||
let intersections2 = bezier2.intersections(&bezier1, None, None);
|
||||
|
||||
let intersections1_points: Vec<DVec2> = intersections1.iter().map(|&t| bezier1.evaluate(ComputeType::Parametric(t))).collect();
|
||||
let intersections2_points: Vec<DVec2> = intersections2.iter().map(|&t| bezier2.evaluate(ComputeType::Parametric(t))).rev().collect();
|
||||
let intersections1_points: Vec<DVec2> = intersections1.iter().map(|&t| bezier1.evaluate(TValue::Parametric(t))).collect();
|
||||
let intersections2_points: Vec<DVec2> = intersections2.iter().map(|&t| bezier2.evaluate(TValue::Parametric(t))).rev().collect();
|
||||
|
||||
assert!(compare_vec_of_points(intersections1_points, intersections2_points, 2.));
|
||||
}
|
||||
|
@ -710,8 +711,8 @@ mod tests {
|
|||
let bezier = Bezier::from_cubic_coordinates(160., 180., 170., 10., 30., 90., 180., 140.);
|
||||
let intersections = bezier.self_intersections(Some(0.5));
|
||||
assert!(compare_vec_of_points(
|
||||
intersections.iter().map(|&t| bezier.evaluate(ComputeType::Parametric(t[0]))).collect(),
|
||||
intersections.iter().map(|&t| bezier.evaluate(ComputeType::Parametric(t[1]))).collect(),
|
||||
intersections.iter().map(|&t| bezier.evaluate(TValue::Parametric(t[0]))).collect(),
|
||||
intersections.iter().map(|&t| bezier.evaluate(TValue::Parametric(t[1]))).collect(),
|
||||
2.
|
||||
));
|
||||
assert!(Bezier::from_linear_coordinates(160., 180., 170., 10.).self_intersections(None).is_empty());
|
||||
|
|
|
@ -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
|
||||
));
|
||||
|
|
|
@ -10,6 +10,8 @@ pub const NUM_DISTANCES: usize = 5;
|
|||
pub const SCALABLE_CURVE_MAX_ENDPOINT_NORMAL_ANGLE: f64 = std::f64::consts::PI / 3.;
|
||||
/// Minimum allowable separation between adjacent `t` values when calculating curve intersections
|
||||
pub const MIN_SEPERATION_VALUE: f64 = 5. * 1e-3;
|
||||
/// Default error bound for `t_value_to_parametric` function when TValue argument is Euclidean
|
||||
pub const DEFAULT_EUCLIDEAN_ERROR_BOUND: f64 = 0.001;
|
||||
|
||||
// Method argument defaults
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@ mod utils;
|
|||
|
||||
pub use bezier::*;
|
||||
pub use subpath::*;
|
||||
pub use utils::ComputeType;
|
||||
pub use utils::TValue;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -4,10 +4,18 @@ use glam::{BVec2, DMat2, DVec2};
|
|||
use std::f64::consts::PI;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum ComputeType {
|
||||
/// A structure which can be used to reference a particular point along a `Bezier`.
|
||||
/// Assuming a 2-dimensional Bezier is represented as a parametric curve defined by components `(x(f(t), y(f(t))))`, this structure defines variants for `f(t)`.
|
||||
/// - The `Parametric` variant represents the point calculated using the parametric equation of the curve at argument `t`. That is, `f(t) = t`. Speed along the curve's parametric form is not constant. `t` must lie in the range `[0, 1]`.
|
||||
/// - The `Euclidean` variant represents the point calculated at a distance ratio `t` along the arc length of the curve in the range `[0, 1]`. Speed is constant along the curve's arc length.
|
||||
/// - E.g. If `d` is the distance from the start point of a `Bezier` to a certain point along the curve, and `l` is the total arc length of the curve, that certain point lies at a distance ratio `t = d / l`.
|
||||
/// - All `Bezier` functions will implicitly convert a Euclidean [TValue] argument to a parametric `t`-value using binary search, computed within a particular error. That is, a point at distance ratio `t*`,
|
||||
/// satisfying `|t* - t| <= error`. The default error is `0.001`. Given this requires a lengthier calculation, it is not recommended to use the `Euclidean` or `EuclideanWithinError` variants frequently in computationally intensive tasks.
|
||||
/// - The `EuclideanWithinError` variant functions exactly as the `Euclidean` variant, but allows the `error` to be customized when computing `t` internally.
|
||||
pub enum TValue {
|
||||
Parametric(f64),
|
||||
Euclidean(f64),
|
||||
EuclideanWithinError { t: f64, epsilon: f64 },
|
||||
EuclideanWithinError { t: f64, error: f64 },
|
||||
}
|
||||
|
||||
/// Helper to perform the computation of a and c, where b is the provided point on the curve.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { WasmBezier } from "@/../wasm/pkg";
|
||||
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
||||
import { renderDemo } from "@/utils/render";
|
||||
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, ComputeType, Demo } from "@/utils/types";
|
||||
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, TVariant, Demo } from "@/utils/types";
|
||||
|
||||
const SELECTABLE_RANGE = 10;
|
||||
|
||||
|
@ -24,7 +24,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
|
||||
triggerOnMouseMove!: boolean;
|
||||
|
||||
computeType!: ComputeType;
|
||||
tVariant!: TVariant;
|
||||
|
||||
// Data
|
||||
bezier!: WasmBezier;
|
||||
|
@ -40,12 +40,12 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
sliderUnits!: Record<string, string | string[]>;
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ["computetype"];
|
||||
return ["tvariant"];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
||||
if (name === "computetype" && oldValue) {
|
||||
this.computeType = (newValue || "Parametric") as ComputeType;
|
||||
if (name === "tvariant" && oldValue) {
|
||||
this.tVariant = (newValue || "Parametric") as TVariant;
|
||||
const figure = this.querySelector("figure") as HTMLElement;
|
||||
this.drawDemo(figure);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
this.key = this.getAttribute("key") as BezierFeatureKey;
|
||||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||
this.computeType = (this.getAttribute("computetype") || "Parametric") as ComputeType;
|
||||
this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant;
|
||||
|
||||
this.callback = bezierFeatures[this.key].callback as BezierCallback;
|
||||
const curveType = getCurveType(this.points.length);
|
||||
|
@ -78,7 +78,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
}
|
||||
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation, this.computeType);
|
||||
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation, this.tVariant);
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent): void {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
|
||||
import { renderDemoPane } from "@/utils/render";
|
||||
import { BezierCurveType, BEZIER_CURVE_TYPE, ComputeType, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
|
||||
import { BezierCurveType, BEZIER_CURVE_TYPE, TVariant, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
|
||||
|
||||
const demoDefaults = {
|
||||
Linear: {
|
||||
|
@ -36,24 +36,23 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
|||
|
||||
triggerOnMouseMove!: boolean;
|
||||
|
||||
chooseComputeType!: boolean;
|
||||
chooseTVariant!: boolean;
|
||||
|
||||
// Data
|
||||
demos!: BezierDemoArgs[];
|
||||
|
||||
id!: string;
|
||||
|
||||
computeType!: ComputeType;
|
||||
tVariant!: TVariant;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.computeType = "Parametric";
|
||||
|
||||
this.tVariant = "Parametric";
|
||||
this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
|
||||
this.id = `bezier/${this.key}`;
|
||||
this.name = bezierFeatures[this.key].name;
|
||||
this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]");
|
||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
|
||||
this.chooseTVariant = this.getAttribute("chooseTVariant") === "true";
|
||||
// Use quadratic slider options as a default if sliders are not provided for the other curve types.
|
||||
const defaultSliderOptions: SliderOption[] = this.demoOptions.Quadratic?.sliderOptions || [];
|
||||
this.demos = BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => {
|
||||
|
@ -80,7 +79,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
|||
bezierDemo.setAttribute("key", this.key);
|
||||
bezierDemo.setAttribute("sliderOptions", JSON.stringify(demo.sliderOptions));
|
||||
bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
||||
bezierDemo.setAttribute("computetype", this.computeType);
|
||||
bezierDemo.setAttribute("tvariant", this.tVariant);
|
||||
return bezierDemo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { WasmSubpath } from "@/../wasm/pkg";
|
|||
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
||||
import { renderDemo } from "@/utils/render";
|
||||
|
||||
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, ComputeType } from "@/utils/types";
|
||||
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, TVariant } from "@/utils/types";
|
||||
|
||||
const SELECTABLE_RANGE = 10;
|
||||
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
|
||||
|
@ -21,7 +21,7 @@ class SubpathDemo extends HTMLElement {
|
|||
|
||||
triggerOnMouseMove!: boolean;
|
||||
|
||||
computeType!: ComputeType;
|
||||
tVariant!: TVariant;
|
||||
|
||||
// Data
|
||||
subpath!: WasmSubpath;
|
||||
|
@ -37,12 +37,12 @@ class SubpathDemo extends HTMLElement {
|
|||
sliderUnits!: Record<string, string | string[]>;
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ["computetype"];
|
||||
return ["tvariant"];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
||||
if (name === "computetype" && oldValue) {
|
||||
this.computeType = (newValue || "Parametric") as ComputeType;
|
||||
if (name === "tvariant" && oldValue) {
|
||||
this.tVariant = (newValue || "Parametric") as TVariant;
|
||||
const figure = this.querySelector("figure") as HTMLElement;
|
||||
this.drawDemo(figure);
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class SubpathDemo extends HTMLElement {
|
|||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||
this.closed = this.getAttribute("closed") === "true";
|
||||
this.computeType = (this.getAttribute("computetype") || "Parametric") as ComputeType;
|
||||
this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant;
|
||||
|
||||
this.callback = subpathFeatures[this.key].callback as SubpathCallback;
|
||||
this.subpath = WasmSubpath.from_triples(this.triples, this.closed) as WasmSubpathInstance;
|
||||
|
@ -73,7 +73,7 @@ class SubpathDemo extends HTMLElement {
|
|||
}
|
||||
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation, this.computeType);
|
||||
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation, this.tVariant);
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent): void {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
|
||||
import { renderDemoPane } from "@/utils/render";
|
||||
import { ComputeType, Demo, DemoPane, SliderOption, SubpathDemoArgs } from "@/utils/types";
|
||||
import { TVariant, Demo, DemoPane, SliderOption, SubpathDemoArgs } from "@/utils/types";
|
||||
|
||||
class SubpathDemoPane extends HTMLElement implements DemoPane {
|
||||
// Props
|
||||
|
@ -12,14 +12,14 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
|||
|
||||
triggerOnMouseMove!: boolean;
|
||||
|
||||
chooseComputeType!: boolean;
|
||||
chooseTVariant!: boolean;
|
||||
|
||||
// Data
|
||||
demos!: SubpathDemoArgs[];
|
||||
|
||||
id!: string;
|
||||
|
||||
computeType!: ComputeType;
|
||||
tVariant!: TVariant;
|
||||
|
||||
connectedCallback(): void {
|
||||
this.demos = [
|
||||
|
@ -47,14 +47,13 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
|||
closed: true,
|
||||
},
|
||||
];
|
||||
this.computeType = "Parametric";
|
||||
|
||||
this.tVariant = "Parametric";
|
||||
this.key = (this.getAttribute("name") || "") as SubpathFeatureKey;
|
||||
this.id = `subpath/${this.key}`;
|
||||
this.name = subpathFeatures[this.key].name;
|
||||
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
|
||||
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
|
||||
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
|
||||
this.chooseTVariant = this.getAttribute("chooseTVariant") === "true";
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
@ -71,7 +70,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
|||
subpathDemo.setAttribute("key", this.key);
|
||||
subpathDemo.setAttribute("sliderOptions", JSON.stringify(this.sliderOptions));
|
||||
subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
|
||||
subpathDemo.setAttribute("computetype", this.computeType);
|
||||
subpathDemo.setAttribute("tvariant", this.tVariant);
|
||||
return subpathDemo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { WasmBezier } from "@/../wasm/pkg";
|
||||
import { tSliderOptions, tErrorOptions, tMinimumSeperationOptions } from "@/utils/options";
|
||||
import { ComputeType, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types";
|
||||
import { tSliderOptions, errorOptions, minimumSeparationOptions } from "@/utils/options";
|
||||
import { TVariant, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types";
|
||||
|
||||
const bezierFeatures = {
|
||||
constructor: {
|
||||
|
@ -67,13 +67,13 @@ const bezierFeatures = {
|
|||
},
|
||||
evaluate: {
|
||||
name: "Evaluate",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => bezier.evaluate(options.computeArgument, computeType),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.evaluate(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
|
||||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseComputeType: true,
|
||||
chooseTVariant: true,
|
||||
},
|
||||
"lookup-table": {
|
||||
name: "Lookup Table",
|
||||
|
@ -118,25 +118,27 @@ const bezierFeatures = {
|
|||
},
|
||||
tangent: {
|
||||
name: "Tangent",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.tangent(options.t),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.tangent(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
normal: {
|
||||
name: "Normal",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.normal(options.t),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.normal(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
curvature: {
|
||||
name: "Curvature",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.curvature(options.t),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.curvature(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Linear: {
|
||||
disabled: true,
|
||||
|
@ -145,19 +147,21 @@ const bezierFeatures = {
|
|||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
split: {
|
||||
name: "Split",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.split(options.t),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.split(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
trim: {
|
||||
name: "Trim",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.trim(options.t1, options.t2),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.trim(options.t1, options.t2, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [
|
||||
|
@ -178,6 +182,7 @@ const bezierFeatures = {
|
|||
],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
project: {
|
||||
name: "Project",
|
||||
|
@ -400,11 +405,11 @@ const bezierFeatures = {
|
|||
[180, 10],
|
||||
[90, 120],
|
||||
];
|
||||
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_seperation);
|
||||
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_separation);
|
||||
},
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
|
||||
sliderOptions: [errorOptions, minimumSeparationOptions],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -417,11 +422,11 @@ const bezierFeatures = {
|
|||
[40, 120],
|
||||
[175, 140],
|
||||
];
|
||||
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_seperation);
|
||||
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_separation);
|
||||
},
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
|
||||
sliderOptions: [errorOptions, minimumSeparationOptions],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -430,7 +435,7 @@ const bezierFeatures = {
|
|||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tErrorOptions],
|
||||
sliderOptions: [errorOptions],
|
||||
},
|
||||
Cubic: {
|
||||
customPoints: [
|
||||
|
@ -470,12 +475,13 @@ const bezierFeatures = {
|
|||
},
|
||||
"de-casteljau-points": {
|
||||
name: "De Casteljau Points",
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.de_casteljau_points(options.t),
|
||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => bezier.de_casteljau_points(options.t, tVariant),
|
||||
demoOptions: {
|
||||
Quadratic: {
|
||||
sliderOptions: [tSliderOptions],
|
||||
},
|
||||
},
|
||||
chooseTVariant: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -485,6 +491,6 @@ export type BezierFeatureOptions = {
|
|||
callback: BezierCallback;
|
||||
demoOptions?: Partial<BezierDemoOptions>;
|
||||
triggerOnMouseMove?: boolean;
|
||||
chooseComputeType?: boolean;
|
||||
chooseTVariant?: boolean;
|
||||
};
|
||||
export default bezierFeatures as Record<BezierFeatureKey, BezierFeatureOptions>;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { tSliderOptions } from "@/utils/options";
|
||||
import { ComputeType, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types";
|
||||
import { TVariant, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types";
|
||||
|
||||
const subpathFeatures = {
|
||||
constructor: {
|
||||
|
@ -8,10 +8,10 @@ const subpathFeatures = {
|
|||
},
|
||||
insert: {
|
||||
name: "Insert",
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.insert(options.computeArgument, computeType),
|
||||
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.insert(options.t, tVariant),
|
||||
sliderOptions: [tSliderOptions],
|
||||
// TODO: Uncomment this after implementing the Euclidean version
|
||||
// chooseComputeType: true,
|
||||
// chooseTVariant: true,
|
||||
},
|
||||
length: {
|
||||
name: "Length",
|
||||
|
@ -19,9 +19,9 @@ const subpathFeatures = {
|
|||
},
|
||||
evaluate: {
|
||||
name: "Evaluate",
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.evaluate(options.computeArgument, computeType),
|
||||
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
|
||||
chooseComputeType: true,
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.evaluate(options.t, tVariant),
|
||||
sliderOptions: [tSliderOptions],
|
||||
chooseTVariant: true,
|
||||
},
|
||||
project: {
|
||||
name: "Project",
|
||||
|
@ -68,10 +68,10 @@ const subpathFeatures = {
|
|||
},
|
||||
split: {
|
||||
name: "Split",
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.split(options.computeArgument, computeType),
|
||||
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
|
||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, tVariant: TVariant): string => subpath.split(options.t, tVariant),
|
||||
sliderOptions: [tSliderOptions],
|
||||
// TODO: Uncomment this after implementing the Euclidean version
|
||||
// chooseComputeType: true,
|
||||
// chooseTVariant: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -81,6 +81,6 @@ export type SubpathFeatureOptions = {
|
|||
callback: SubpathCallback;
|
||||
sliderOptions?: SliderOption[];
|
||||
triggerOnMouseMove?: boolean;
|
||||
chooseComputeType?: boolean;
|
||||
chooseTVariant?: boolean;
|
||||
};
|
||||
export default subpathFeatures as Record<SubpathFeatureKey, SubpathFeatureOptions>;
|
||||
|
|
|
@ -31,7 +31,7 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement
|
|||
demo.setAttribute("name", featureName);
|
||||
demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {}));
|
||||
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
||||
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
|
||||
demo.setAttribute("chooseTVariant", String(feature.chooseTVariant));
|
||||
container?.append(demo);
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElemen
|
|||
demo.setAttribute("name", featureName);
|
||||
demo.setAttribute("sliderOptions", JSON.stringify(feature.sliderOptions || []));
|
||||
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
|
||||
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
|
||||
demo.setAttribute("chooseTVariant", String(feature.chooseTVariant));
|
||||
container?.append(demo);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ body > h2 {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.compute-type-choice {
|
||||
.t-variant-choice {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ export const tSliderOptions = {
|
|||
variable: "t",
|
||||
};
|
||||
|
||||
export const tErrorOptions = {
|
||||
export const errorOptions = {
|
||||
variable: "error",
|
||||
min: 0.1,
|
||||
max: 2,
|
||||
|
@ -14,7 +14,7 @@ export const tErrorOptions = {
|
|||
default: 0.5,
|
||||
};
|
||||
|
||||
export const tMinimumSeperationOptions = {
|
||||
export const minimumSeparationOptions = {
|
||||
variable: "minimum_seperation",
|
||||
min: 0.001,
|
||||
max: 0.25,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ComputeType, Demo, DemoPane, SliderOption } from "@/utils/types";
|
||||
import { TVariant, Demo, DemoPane, SliderOption } from "@/utils/types";
|
||||
|
||||
export function renderDemo(demo: Demo): void {
|
||||
const header = document.createElement("h4");
|
||||
|
@ -53,27 +53,27 @@ export function renderDemoPane(demoPane: DemoPane): void {
|
|||
header.className = "demo-pane-header";
|
||||
header.append(headerAnchorLink);
|
||||
|
||||
const computeTypeContainer = document.createElement("div");
|
||||
computeTypeContainer.className = "compute-type-choice";
|
||||
const tVariantContainer = document.createElement("div");
|
||||
tVariantContainer.className = "t-variant-choice";
|
||||
|
||||
const computeTypeLabel = document.createElement("strong");
|
||||
computeTypeLabel.innerText = "ComputeType:";
|
||||
computeTypeContainer.append(computeTypeLabel);
|
||||
const tVariantLabel = document.createElement("strong");
|
||||
tVariantLabel.innerText = "TValue Variant:";
|
||||
tVariantContainer.append(tVariantLabel);
|
||||
|
||||
const radioInputs = ["Parametric", "Euclidean"].map((computeType) => {
|
||||
const id = `${demoPane.id}-${computeType}`;
|
||||
const radioInputs = ["Parametric", "Euclidean"].map((tVariant) => {
|
||||
const id = `${demoPane.id}-${tVariant}`;
|
||||
const radioInput = document.createElement("input");
|
||||
radioInput.type = "radio";
|
||||
radioInput.id = id;
|
||||
radioInput.value = computeType;
|
||||
radioInput.name = "ComputeType";
|
||||
radioInput.checked = computeType === "Parametric";
|
||||
computeTypeContainer.append(radioInput);
|
||||
radioInput.value = tVariant;
|
||||
radioInput.name = `TVariant - ${demoPane.id}`;
|
||||
radioInput.checked = tVariant === "Parametric";
|
||||
tVariantContainer.append(radioInput);
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.htmlFor = id;
|
||||
label.innerText = computeType;
|
||||
computeTypeContainer.append(label);
|
||||
label.innerText = tVariant;
|
||||
tVariantContainer.append(label);
|
||||
return radioInput;
|
||||
});
|
||||
|
||||
|
@ -88,16 +88,16 @@ export function renderDemoPane(demoPane: DemoPane): void {
|
|||
|
||||
radioInputs.forEach((radioInput: HTMLElement) => {
|
||||
radioInput.addEventListener("input", (event: Event): void => {
|
||||
demoPane.computeType = (event.target as HTMLInputElement).value as ComputeType;
|
||||
demoComponent.setAttribute("computetype", demoPane.computeType);
|
||||
demoPane.tVariant = (event.target as HTMLInputElement).value as TVariant;
|
||||
demoComponent.setAttribute("tvariant", demoPane.tVariant);
|
||||
});
|
||||
});
|
||||
demoRow.append(demoComponent);
|
||||
});
|
||||
|
||||
container.append(header);
|
||||
if (demoPane.chooseComputeType) {
|
||||
container.append(computeTypeContainer);
|
||||
if (demoPane.chooseTVariant) {
|
||||
container.append(tVariantContainer);
|
||||
}
|
||||
container.append(demoRow);
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_ou
|
|||
export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const;
|
||||
export type BezierCurveType = typeof BEZIER_CURVE_TYPE[number];
|
||||
|
||||
export type ComputeType = "Euclidean" | "Parametric";
|
||||
export type TVariant = "Euclidean" | "Parametric";
|
||||
|
||||
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string;
|
||||
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string;
|
||||
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number], tVariant?: TVariant) => string;
|
||||
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number], tVariant?: TVariant) => string;
|
||||
|
||||
export type BezierDemoOptions = {
|
||||
[key in BezierCurveType]: {
|
||||
|
@ -85,7 +85,7 @@ export interface DemoPane extends HTMLElement {
|
|||
name: string;
|
||||
demos: DemoArgs[];
|
||||
id: string;
|
||||
chooseComputeType: boolean;
|
||||
computeType: ComputeType;
|
||||
chooseTVariant: boolean;
|
||||
tVariant: TVariant;
|
||||
buildDemo(demo: DemoArgs): Demo;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::svg_drawing::*;
|
||||
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ComputeType, ProjectionOptions};
|
||||
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ProjectionOptions, TValue};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -41,6 +41,14 @@ fn convert_wasm_maximize_arcs(wasm_enum_value: WasmMaximizeArcs) -> ArcStrategy
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_t_variant(t_variant: &String, t: f64) -> TValue {
|
||||
match t_variant.as_str() {
|
||||
"Parametric" => TValue::Parametric(t),
|
||||
"Euclidean" => TValue::Euclidean(t),
|
||||
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmBezier {
|
||||
/// Expect js_points to be a list of 2 pairs.
|
||||
|
@ -128,13 +136,10 @@ impl WasmBezier {
|
|||
wrap_svg_tag(format!("{bezier}{}", draw_text(format!("Length: {:.2}", self.0.length(None)), TEXT_OFFSET_X, TEXT_OFFSET_Y, BLACK)))
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, t: f64, compute_type: String) -> String {
|
||||
pub fn evaluate(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let bezier = self.get_bezier_path();
|
||||
let point = match compute_type.as_str() {
|
||||
"Euclidean" => self.0.evaluate(ComputeType::Euclidean(t)),
|
||||
"Parametric" => self.0.evaluate(ComputeType::Parametric(t)),
|
||||
_ => panic!("Unexpected ComputeType string: '{}'", compute_type),
|
||||
};
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
let point = self.0.evaluate(t);
|
||||
let content = format!("{bezier}{}", draw_circle(point, 4., RED, 1.5, WHITE));
|
||||
wrap_svg_tag(content)
|
||||
}
|
||||
|
@ -169,11 +174,12 @@ impl WasmBezier {
|
|||
wrap_svg_tag(content)
|
||||
}
|
||||
|
||||
pub fn tangent(&self, t: f64) -> String {
|
||||
pub fn tangent(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let bezier = self.get_bezier_path();
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
|
||||
let tangent_point = self.0.tangent(t);
|
||||
let intersection_point = self.0.evaluate(ComputeType::Parametric(t));
|
||||
let intersection_point = self.0.evaluate(t);
|
||||
let tangent_end = intersection_point + tangent_point * SCALE_UNIT_VECTOR_FACTOR;
|
||||
|
||||
let content = format!(
|
||||
|
@ -185,11 +191,12 @@ impl WasmBezier {
|
|||
wrap_svg_tag(content)
|
||||
}
|
||||
|
||||
pub fn normal(&self, t: f64) -> String {
|
||||
pub fn normal(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let bezier = self.get_bezier_path();
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
|
||||
let normal_point = self.0.normal(t);
|
||||
let intersection_point = self.0.evaluate(ComputeType::Parametric(t));
|
||||
let intersection_point = self.0.evaluate(t);
|
||||
let normal_end = intersection_point + normal_point * SCALE_UNIT_VECTOR_FACTOR;
|
||||
|
||||
let content = format!(
|
||||
|
@ -201,11 +208,13 @@ impl WasmBezier {
|
|||
wrap_svg_tag(content)
|
||||
}
|
||||
|
||||
pub fn curvature(&self, t: f64) -> String {
|
||||
pub fn curvature(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let bezier = self.get_bezier_path();
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
|
||||
let radius = 1. / self.0.curvature(t);
|
||||
let normal_point = self.0.normal(t);
|
||||
let intersection_point = self.0.evaluate(ComputeType::Parametric(t));
|
||||
let intersection_point = self.0.evaluate(t);
|
||||
|
||||
let curvature_center = intersection_point + normal_point * radius;
|
||||
|
||||
|
@ -219,7 +228,8 @@ impl WasmBezier {
|
|||
wrap_svg_tag(content)
|
||||
}
|
||||
|
||||
pub fn split(&self, t: f64) -> String {
|
||||
pub fn split(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
let beziers: [Bezier; 2] = self.0.split(t);
|
||||
|
||||
let mut original_bezier_svg = String::new();
|
||||
|
@ -252,7 +262,8 @@ impl WasmBezier {
|
|||
wrap_svg_tag(format!("{original_bezier_svg}{bezier_svg_1}{bezier_svg_2}"))
|
||||
}
|
||||
|
||||
pub fn trim(&self, t1: f64, t2: f64) -> String {
|
||||
pub fn trim(&self, raw_t1: f64, raw_t2: f64, t_variant: String) -> String {
|
||||
let (t1, t2) = (parse_t_variant(&t_variant, raw_t1), parse_t_variant(&t_variant, raw_t2));
|
||||
let trimmed_bezier = self.0.trim(t1, t2);
|
||||
|
||||
let mut trimmed_bezier_svg = String::new();
|
||||
|
@ -269,7 +280,7 @@ impl WasmBezier {
|
|||
|
||||
pub fn project(&self, x: f64, y: f64) -> String {
|
||||
let projected_t_value = self.0.project(DVec2::new(x, y), ProjectionOptions::default());
|
||||
let projected_point = self.0.evaluate(ComputeType::Parametric(projected_t_value));
|
||||
let projected_point = self.0.evaluate(TValue::Parametric(projected_t_value));
|
||||
|
||||
let bezier = self.get_bezier_path();
|
||||
let content = format!("{bezier}{}", draw_line(projected_point.x, projected_point.y, x, y, RED, 1.),);
|
||||
|
@ -285,7 +296,7 @@ impl WasmBezier {
|
|||
.zip([RED, GREEN])
|
||||
.flat_map(|(t_value_list, color)| {
|
||||
t_value_list.iter().map(|&t_value| {
|
||||
let point = self.0.evaluate(ComputeType::Parametric(t_value));
|
||||
let point = self.0.evaluate(TValue::Parametric(t_value));
|
||||
draw_circle(point, 3., color, 1.5, WHITE)
|
||||
})
|
||||
})
|
||||
|
@ -320,7 +331,7 @@ impl WasmBezier {
|
|||
let circles: String = inflections
|
||||
.iter()
|
||||
.map(|&t_value| {
|
||||
let point = self.0.evaluate(ComputeType::Parametric(t_value));
|
||||
let point = self.0.evaluate(TValue::Parametric(t_value));
|
||||
draw_circle(point, 3., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold("".to_string(), |acc, circle| acc + &circle);
|
||||
|
@ -328,7 +339,8 @@ impl WasmBezier {
|
|||
wrap_svg_tag(content)
|
||||
}
|
||||
|
||||
pub fn de_casteljau_points(&self, t: f64) -> String {
|
||||
pub fn de_casteljau_points(&self, raw_t: f64, t_variant: String) -> String {
|
||||
let t = parse_t_variant(&t_variant, raw_t);
|
||||
let points: Vec<Vec<DVec2>> = self.0.de_casteljau_points(t);
|
||||
|
||||
let bezier_svg = self.get_bezier_path();
|
||||
|
@ -413,7 +425,7 @@ impl WasmBezier {
|
|||
.intersect(&line, None, None)
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = &self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = &self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -433,7 +445,7 @@ impl WasmBezier {
|
|||
.intersect(&quadratic, Some(error), Some(minimum_separation))
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = &self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = &self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -453,7 +465,7 @@ impl WasmBezier {
|
|||
.intersect(&cubic, Some(error), Some(minimum_separation))
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = &self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = &self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -469,7 +481,7 @@ impl WasmBezier {
|
|||
.self_intersections(Some(error))
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = &self.0.evaluate(ComputeType::Parametric(intersection_t[0]));
|
||||
let point = &self.0.evaluate(TValue::Parametric(intersection_t[0]));
|
||||
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(bezier_curve_svg, |acc, item| format!("{acc}{item}"));
|
||||
|
@ -497,7 +509,7 @@ impl WasmBezier {
|
|||
.rectangle_intersections(points[0], points[1])
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = &self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = &self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::svg_drawing::*;
|
||||
|
||||
use bezier_rs::{Bezier, ComputeType, ManipulatorGroup, ProjectionOptions, Subpath};
|
||||
use bezier_rs::{Bezier, ManipulatorGroup, ProjectionOptions, Subpath, TValue};
|
||||
|
||||
use glam::DVec2;
|
||||
use std::fmt::Write;
|
||||
|
@ -56,20 +56,20 @@ impl WasmSubpath {
|
|||
subpath_svg
|
||||
}
|
||||
|
||||
pub fn insert(&self, t: f64, compute_type: String) -> String {
|
||||
pub fn insert(&self, t: f64, t_variant: String) -> String {
|
||||
let mut subpath = self.0.clone();
|
||||
let point = match compute_type.as_str() {
|
||||
let point = match t_variant.as_str() {
|
||||
"Euclidean" => {
|
||||
let parameter = ComputeType::Euclidean(t);
|
||||
let parameter = TValue::Euclidean(t);
|
||||
subpath.insert(parameter);
|
||||
self.0.evaluate(parameter)
|
||||
}
|
||||
"Parametric" => {
|
||||
let parameter = ComputeType::Parametric(t);
|
||||
let parameter = TValue::Parametric(t);
|
||||
subpath.insert(parameter);
|
||||
self.0.evaluate(parameter)
|
||||
}
|
||||
_ => panic!("Unexpected ComputeType string: '{}'", compute_type),
|
||||
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||
};
|
||||
let point_text = draw_circle(point, 4., RED, 1.5, WHITE);
|
||||
|
||||
|
@ -81,19 +81,19 @@ impl WasmSubpath {
|
|||
wrap_svg_tag(format!("{}{}", self.to_default_svg(), length_text))
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, t: f64, compute_type: String) -> String {
|
||||
let point = match compute_type.as_str() {
|
||||
"Euclidean" => self.0.evaluate(ComputeType::Euclidean(t)),
|
||||
"Parametric" => self.0.evaluate(ComputeType::Parametric(t)),
|
||||
_ => panic!("Unexpected ComputeType string: '{}'", compute_type),
|
||||
pub fn evaluate(&self, t: f64, t_variant: String) -> String {
|
||||
let point = match t_variant.as_str() {
|
||||
"Euclidean" => self.0.evaluate(TValue::Euclidean(t)),
|
||||
"Parametric" => self.0.evaluate(TValue::Parametric(t)),
|
||||
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||
};
|
||||
let point_text = draw_circle(point, 4., RED, 1.5, WHITE);
|
||||
wrap_svg_tag(format!("{}{}", self.to_default_svg(), point_text))
|
||||
}
|
||||
|
||||
pub fn tangent(&self, t: f64) -> String {
|
||||
let intersection_point = self.0.evaluate(ComputeType::Parametric(t));
|
||||
let tangent_point = self.0.tangent(ComputeType::Parametric(t));
|
||||
let intersection_point = self.0.evaluate(TValue::Parametric(t));
|
||||
let tangent_point = self.0.tangent(TValue::Parametric(t));
|
||||
let tangent_end = intersection_point + tangent_point * SCALE_UNIT_VECTOR_FACTOR;
|
||||
|
||||
let point_text = draw_circle(intersection_point, 4., RED, 1.5, WHITE);
|
||||
|
@ -103,8 +103,8 @@ impl WasmSubpath {
|
|||
}
|
||||
|
||||
pub fn normal(&self, t: f64) -> String {
|
||||
let intersection_point = self.0.evaluate(ComputeType::Parametric(t));
|
||||
let normal_point = self.0.normal(ComputeType::Parametric(t));
|
||||
let intersection_point = self.0.evaluate(TValue::Parametric(t));
|
||||
let normal_point = self.0.normal(TValue::Parametric(t));
|
||||
let normal_end = intersection_point + normal_point * SCALE_UNIT_VECTOR_FACTOR;
|
||||
|
||||
let point_text = draw_circle(intersection_point, 4., RED, 1.5, WHITE);
|
||||
|
@ -115,7 +115,7 @@ impl WasmSubpath {
|
|||
|
||||
pub fn project(&self, x: f64, y: f64) -> String {
|
||||
let (segment_index, projected_t) = self.0.project(DVec2::new(x, y), ProjectionOptions::default()).unwrap();
|
||||
let projected_point = self.0.evaluate(ComputeType::Parametric((segment_index as f64 + projected_t) / (self.0.len_segments() as f64)));
|
||||
let projected_point = self.0.evaluate(TValue::Parametric((segment_index as f64 + projected_t) / (self.0.len_segments() as f64)));
|
||||
|
||||
let subpath_svg = self.to_default_svg();
|
||||
let content = format!("{subpath_svg}{}", draw_line(projected_point.x, projected_point.y, x, y, RED, 1.),);
|
||||
|
@ -143,7 +143,7 @@ impl WasmSubpath {
|
|||
.intersections(&line, None, None)
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -172,7 +172,7 @@ impl WasmSubpath {
|
|||
.intersections(&line, None, None)
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -201,7 +201,7 @@ impl WasmSubpath {
|
|||
.intersections(&line, None, None)
|
||||
.iter()
|
||||
.map(|intersection_t| {
|
||||
let point = self.0.evaluate(ComputeType::Parametric(*intersection_t));
|
||||
let point = self.0.evaluate(TValue::Parametric(*intersection_t));
|
||||
draw_circle(point, 4., RED, 1.5, WHITE)
|
||||
})
|
||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||
|
@ -209,11 +209,11 @@ impl WasmSubpath {
|
|||
wrap_svg_tag(format!("{subpath_svg}{line_svg}{intersections_svg}"))
|
||||
}
|
||||
|
||||
pub fn split(&self, t: f64, compute_type: String) -> String {
|
||||
let (main_subpath, optional_subpath) = match compute_type.as_str() {
|
||||
"Euclidean" => self.0.split(ComputeType::Euclidean(t)),
|
||||
"Parametric" => self.0.split(ComputeType::Parametric(t)),
|
||||
_ => panic!("Unexpected ComputeType string: '{}'", compute_type),
|
||||
pub fn split(&self, t: f64, t_variant: String) -> String {
|
||||
let (main_subpath, optional_subpath) = match t_variant.as_str() {
|
||||
"Euclidean" => self.0.split(TValue::Euclidean(t)),
|
||||
"Parametric" => self.0.split(TValue::Parametric(t)),
|
||||
_ => panic!("Unexpected ComputeType string: '{}'", t_variant),
|
||||
};
|
||||
|
||||
let mut main_subpath_svg = String::new();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue