From 511a8aa164726ed2399fa4075691dabe9a7b3748 Mon Sep 17 00:00:00 2001 From: Jackie Chen <46739769+jackiechen73@users.noreply.github.com> Date: Tue, 31 Jan 2023 00:15:37 -0500 Subject: [PATCH] Bezier-rs: Add normal and tangent to subpath (#1003) tangent and normal for subpath Co-authored-by: Rob Nadal --- libraries/bezier-rs/src/subpath/core.rs | 12 ++++++ libraries/bezier-rs/src/subpath/solvers.rs | 40 +++++++++++++++---- .../src/features/subpath-features.ts | 8 ++++ .../other/bezier-rs-demos/wasm/src/subpath.rs | 24 +++++++++++ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 77a0f367..1938e6e0 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -50,6 +50,18 @@ impl Subpath { number_of_curves } + pub fn find_curve_parametric(&self, t: f64) -> (Option, f64) { + assert!((0.0..=1.).contains(&t)); + + let number_of_curves = self.len_segments() as f64; + let scaled_t = t * number_of_curves; + + let target_curve_index = scaled_t.floor() as i32; + let target_curve_t = scaled_t % 1.; + + (self.iter().nth(target_curve_index as usize), target_curve_t) + } + /// Returns an iterator of the [Bezier]s along the `Subpath`. pub fn iter(&self) -> SubpathIter { SubpathIter { sub_path: self, index: 0 } diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index f904717d..e00f99cb 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -12,13 +12,7 @@ impl Subpath { ComputeType::Parametric(t) => { assert!((0.0..=1.).contains(&t)); - let number_of_curves = self.len_segments() as f64; - let scaled_t = t * number_of_curves; - - let target_curve_index = scaled_t.floor() as i32; - let target_curve_t = scaled_t % 1.; - - if let Some(curve) = self.iter().nth(target_curve_index as usize) { + if let (Some(curve), target_curve_t) = self.find_curve_parametric(t) { curve.evaluate(ComputeType::Parametric(target_curve_t)) } else { self.iter().last().unwrap().evaluate(ComputeType::Parametric(1.)) @@ -59,6 +53,38 @@ impl Subpath { intersection_t_values } + + pub fn tangent(&self, t: ComputeType) -> DVec2 { + match t { + ComputeType::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) + } else { + self.iter().last().unwrap().tangent(1.) + } + } + ComputeType::Euclidean(_t) => unimplemented!(), + ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(), + } + } + + pub fn normal(&self, t: ComputeType) -> DVec2 { + match t { + ComputeType::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) + } else { + self.iter().last().unwrap().normal(1.) + } + } + ComputeType::Euclidean(_t) => unimplemented!(), + ComputeType::EuclideanWithinError { t: _, epsilon: _ } => todo!(), + } + } } #[cfg(test)] diff --git a/website/other/bezier-rs-demos/src/features/subpath-features.ts b/website/other/bezier-rs-demos/src/features/subpath-features.ts index fd4d68e0..086c1a1c 100644 --- a/website/other/bezier-rs-demos/src/features/subpath-features.ts +++ b/website/other/bezier-rs-demos/src/features/subpath-features.ts @@ -24,6 +24,14 @@ const subpathFeatures = { mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(), triggerOnMouseMove: true, }, + Tangent: { + callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.tangent(options.t), + sliderOptions: [tSliderOptions], + }, + Normal: { + callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.normal(options.t), + sliderOptions: [tSliderOptions], + }, "Intersect (Line Segment)": { callback: (subpath: WasmSubpathInstance): string => subpath.intersect_line_segment([ diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index 8108f3da..4411fab8 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -9,6 +9,8 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct WasmSubpath(Subpath); +const SCALE_UNIT_VECTOR_FACTOR: f64 = 50.; + #[wasm_bindgen] impl WasmSubpath { /// Expects js_points to be an unbounded list of triples, where each item is a tuple of floats. @@ -88,6 +90,28 @@ impl WasmSubpath { 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 tangent_end = intersection_point + tangent_point * SCALE_UNIT_VECTOR_FACTOR; + + let point_text = draw_circle(intersection_point, 4., RED, 1.5, WHITE); + let line_text = draw_line(intersection_point.x, intersection_point.y, tangent_end.x, tangent_end.y, RED, 1.); + let tangent_end_point = draw_circle(tangent_end, 3., RED, 1., WHITE); + wrap_svg_tag(format!("{}{}{}{}", self.to_default_svg(), point_text, line_text, tangent_end_point)) + } + + 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 normal_end = intersection_point + normal_point * SCALE_UNIT_VECTOR_FACTOR; + + let point_text = draw_circle(intersection_point, 4., RED, 1.5, WHITE); + let line_text = draw_line(intersection_point.x, intersection_point.y, normal_end.x, normal_end.y, RED, 1.); + let normal_end_point = draw_circle(normal_end, 3., RED, 1., WHITE); + wrap_svg_tag(format!("{}{}{}{}", self.to_default_svg(), point_text, line_text, normal_end_point)) + } + 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)));