Bezier-rs: Add normal and tangent to subpath (#1003)
tangent and normal for subpath Co-authored-by: Rob Nadal <Robnadal44@gmail.com>
This commit is contained in:
parent
beab0f01c6
commit
511a8aa164
|
|
@ -50,6 +50,18 @@ impl Subpath {
|
||||||
number_of_curves
|
number_of_curves
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_curve_parametric(&self, t: f64) -> (Option<Bezier>, 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`.
|
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
||||||
pub fn iter(&self) -> SubpathIter {
|
pub fn iter(&self) -> SubpathIter {
|
||||||
SubpathIter { sub_path: self, index: 0 }
|
SubpathIter { sub_path: self, index: 0 }
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,7 @@ impl Subpath {
|
||||||
ComputeType::Parametric(t) => {
|
ComputeType::Parametric(t) => {
|
||||||
assert!((0.0..=1.).contains(&t));
|
assert!((0.0..=1.).contains(&t));
|
||||||
|
|
||||||
let number_of_curves = self.len_segments() as f64;
|
if let (Some(curve), target_curve_t) = self.find_curve_parametric(t) {
|
||||||
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) {
|
|
||||||
curve.evaluate(ComputeType::Parametric(target_curve_t))
|
curve.evaluate(ComputeType::Parametric(target_curve_t))
|
||||||
} else {
|
} else {
|
||||||
self.iter().last().unwrap().evaluate(ComputeType::Parametric(1.))
|
self.iter().last().unwrap().evaluate(ComputeType::Parametric(1.))
|
||||||
|
|
@ -59,6 +53,38 @@ impl Subpath {
|
||||||
|
|
||||||
intersection_t_values
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,14 @@ const subpathFeatures = {
|
||||||
mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(),
|
mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(),
|
||||||
triggerOnMouseMove: true,
|
triggerOnMouseMove: true,
|
||||||
},
|
},
|
||||||
|
Tangent: {
|
||||||
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.tangent(options.t),
|
||||||
|
sliderOptions: [tSliderOptions],
|
||||||
|
},
|
||||||
|
Normal: {
|
||||||
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.normal(options.t),
|
||||||
|
sliderOptions: [tSliderOptions],
|
||||||
|
},
|
||||||
"Intersect (Line Segment)": {
|
"Intersect (Line Segment)": {
|
||||||
callback: (subpath: WasmSubpathInstance): string =>
|
callback: (subpath: WasmSubpathInstance): string =>
|
||||||
subpath.intersect_line_segment([
|
subpath.intersect_line_segment([
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ use wasm_bindgen::prelude::*;
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct WasmSubpath(Subpath);
|
pub struct WasmSubpath(Subpath);
|
||||||
|
|
||||||
|
const SCALE_UNIT_VECTOR_FACTOR: f64 = 50.;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl WasmSubpath {
|
impl WasmSubpath {
|
||||||
/// Expects js_points to be an unbounded list of triples, where each item is a tuple of floats.
|
/// 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))
|
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 {
|
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 (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(ComputeType::Parametric((segment_index as f64 + projected_t) / (self.0.len_segments() as f64)));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue