Bezier-rs: Insert function for subpath (#876)
* Add manipulator * Add manipulator group * Add UI * Address comments and rebase * Renamed add_manipulator_group to insert Co-authored-by: Rob Nadal <robnadal44@gmail.com>
This commit is contained in:
parent
cd58dcd3dd
commit
758f757783
|
|
@ -0,0 +1,149 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||||
|
use crate::utils::f64_compare;
|
||||||
|
use crate::ComputeType;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
match t {
|
||||||
|
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 f64_compare(target_curve_t, 0., MAX_ABSOLUTE_DIFFERENCE) || f64_compare(target_curve_t, 1., MAX_ABSOLUTE_DIFFERENCE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only case where `curve` would be `None` is if the provided argument was 1
|
||||||
|
// 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 new_group = ManipulatorGroup {
|
||||||
|
anchor: first.end(),
|
||||||
|
in_handle: first.handle_end(),
|
||||||
|
out_handle: second.handle_start(),
|
||||||
|
};
|
||||||
|
let number_of_groups = self.manipulator_groups.len() + 1;
|
||||||
|
self.manipulator_groups.insert((target_curve_index as usize) + 1, new_group);
|
||||||
|
self.manipulator_groups[(target_curve_index as usize) % number_of_groups].out_handle = first.handle_start();
|
||||||
|
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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use glam::DVec2;
|
||||||
|
|
||||||
|
fn set_up_open_subpath() -> Subpath {
|
||||||
|
let start = DVec2::new(20., 30.);
|
||||||
|
let middle1 = DVec2::new(80., 90.);
|
||||||
|
let middle2 = DVec2::new(100., 100.);
|
||||||
|
let end = DVec2::new(60., 45.);
|
||||||
|
|
||||||
|
let handle1 = DVec2::new(75., 85.);
|
||||||
|
let handle2 = DVec2::new(40., 30.);
|
||||||
|
let handle3 = DVec2::new(10., 10.);
|
||||||
|
|
||||||
|
return Subpath::new(
|
||||||
|
vec![
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: start,
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: Some(handle1),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: middle1,
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: Some(handle2),
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: middle2,
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
},
|
||||||
|
ManipulatorGroup {
|
||||||
|
anchor: end,
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: Some(handle3),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_up_closed_subpath() -> Subpath {
|
||||||
|
let mut subpath = set_up_open_subpath();
|
||||||
|
subpath.closed = true;
|
||||||
|
subpath
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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));
|
||||||
|
assert_eq!(subpath.manipulator_groups[3].anchor, location);
|
||||||
|
assert_eq!(subpath.manipulator_groups.len(), 5);
|
||||||
|
assert_eq!(subpath.len_segments(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
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());
|
||||||
|
assert!(subpath.closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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.));
|
||||||
|
assert_eq!(subpath.manipulator_groups[0].anchor, location);
|
||||||
|
assert_eq!(subpath.manipulator_groups.len(), 4);
|
||||||
|
assert!(subpath.closed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod core;
|
mod core;
|
||||||
mod lookup;
|
mod lookup;
|
||||||
|
mod manipulators;
|
||||||
mod solvers;
|
mod solvers;
|
||||||
mod structs;
|
mod structs;
|
||||||
pub use structs::*;
|
pub use structs::*;
|
||||||
|
|
@ -9,6 +10,7 @@ use crate::Bezier;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
/// Structure used to represent a path composed of [Bezier] curves.
|
/// Structure used to represent a path composed of [Bezier] curves.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Subpath {
|
pub struct Subpath {
|
||||||
manipulator_groups: Vec<ManipulatorGroup>,
|
manipulator_groups: Vec<ManipulatorGroup>,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,24 @@
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
use std::fmt::{Debug, Formatter, Result};
|
||||||
|
|
||||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub struct ManipulatorGroup {
|
pub struct ManipulatorGroup {
|
||||||
pub anchor: DVec2,
|
pub anchor: DVec2,
|
||||||
pub in_handle: Option<DVec2>,
|
pub in_handle: Option<DVec2>,
|
||||||
pub out_handle: Option<DVec2>,
|
pub out_handle: Option<DVec2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for ManipulatorGroup {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
|
if self.in_handle.is_some() && self.out_handle.is_some() {
|
||||||
|
write!(f, "anchor: {}, in: {}, out: {}", self.anchor, self.in_handle.unwrap(), self.out_handle.unwrap())
|
||||||
|
} else if self.in_handle.is_some() {
|
||||||
|
write!(f, "anchor: {}, in: {}, out: n/a", self.anchor, self.in_handle.unwrap())
|
||||||
|
} else if self.out_handle.is_some() {
|
||||||
|
write!(f, "anchor: {}, in: n/a, out: {}", self.anchor, self.out_handle.unwrap())
|
||||||
|
} else {
|
||||||
|
write!(f, "anchor: {}, in: n/a, out: n/a", self.anchor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -601,6 +601,13 @@ export default defineComponent({
|
||||||
name: "Constructor",
|
name: "Constructor",
|
||||||
callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
|
callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Insert",
|
||||||
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.insert(options.computeArgument, computeType),
|
||||||
|
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
|
||||||
|
// TODO: Uncomment this after implementing the Euclidean version
|
||||||
|
// chooseComputeType: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Length",
|
name: "Length",
|
||||||
callback: (subpath: WasmSubpathInstance): string => subpath.length(),
|
callback: (subpath: WasmSubpathInstance): string => subpath.length(),
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,26 @@ impl WasmSubpath {
|
||||||
subpath_svg
|
subpath_svg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, t: f64, compute_type: String) -> String {
|
||||||
|
let mut subpath = self.0.clone();
|
||||||
|
let point = match compute_type.as_str() {
|
||||||
|
"Euclidean" => {
|
||||||
|
let parameter = ComputeType::Euclidean(t);
|
||||||
|
subpath.insert(parameter);
|
||||||
|
self.0.evaluate(parameter)
|
||||||
|
}
|
||||||
|
"Parametric" => {
|
||||||
|
let parameter = ComputeType::Parametric(t);
|
||||||
|
subpath.insert(parameter);
|
||||||
|
self.0.evaluate(parameter)
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected ComputeType string: '{}'", compute_type),
|
||||||
|
};
|
||||||
|
let point_text = draw_circle(point, 4., RED, 1.5, WHITE);
|
||||||
|
|
||||||
|
wrap_svg_tag(format!("{}{}", WasmSubpath(subpath).to_default_svg(), point_text))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn length(&self) -> String {
|
pub fn length(&self) -> String {
|
||||||
let length_text = draw_text(format!("Length: {:.2}", self.0.length(None)), 5., 193., BLACK);
|
let length_text = draw_text(format!("Length: {:.2}", self.0.length(None)), 5., 193., BLACK);
|
||||||
wrap_svg_tag(format!("{}{}", self.to_default_svg(), length_text))
|
wrap_svg_tag(format!("{}{}", self.to_default_svg(), length_text))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue