Bezier-rs: Add lookup table function for subpath and make it support euclidean parameterization (#1082)
* Add euclidean option for lut * Add lut for subpath * Fix rust formatting * Fixed breakages caused by UI updates * Code cleanup * Make ProjectionOptions optional --------- Co-authored-by: Rob Nadal <robnadal44@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
d0ac86b713
commit
3733804d18
|
|
@ -375,7 +375,7 @@ impl ShapeState {
|
||||||
|
|
||||||
for subpath in &vector_data.subpaths {
|
for subpath in &vector_data.subpaths {
|
||||||
for (manipulator_index, bezier) in subpath.iter().enumerate() {
|
for (manipulator_index, bezier) in subpath.iter().enumerate() {
|
||||||
let t = bezier.project(layer_pos, projection_options);
|
let t = bezier.project(layer_pos, Some(projection_options));
|
||||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||||
|
|
||||||
let screenspace = transform.transform_point2(layerspace);
|
let screenspace = transform.transform_point2(layerspace);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::utils::{f64_compare, TValue};
|
use crate::utils::{f64_compare, TValue, TValueType};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -74,16 +74,19 @@ impl Bezier {
|
||||||
/// Return a selection of equidistant points on the bezier curve.
|
/// Return a selection of equidistant points on the bezier curve.
|
||||||
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||||
/// <iframe frameBorder="0" width="100%" height="375px" src="https://graphite.rs/bezier-rs-demos#bezier/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="375px" src="https://graphite.rs/bezier-rs-demos#bezier/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
||||||
pub fn compute_lookup_table(&self, steps: Option<usize>) -> Vec<DVec2> {
|
pub fn compute_lookup_table(&self, steps: Option<usize>, tvalue_type: Option<TValueType>) -> Vec<DVec2> {
|
||||||
let steps_unwrapped = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
||||||
let ratio: f64 = 1. / (steps_unwrapped as f64);
|
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric);
|
||||||
let mut steps_array = Vec::with_capacity(steps_unwrapped + 1);
|
|
||||||
|
|
||||||
for t in 0..steps_unwrapped + 1 {
|
(0..=steps)
|
||||||
steps_array.push(self.evaluate(TValue::Parametric(f64::from(t as i32) * ratio)))
|
.map(|t| {
|
||||||
}
|
let tvalue = match tvalue_type {
|
||||||
|
TValueType::Parametric => TValue::Parametric(t as f64 / steps as f64),
|
||||||
steps_array
|
TValueType::Euclidean => TValue::Euclidean(t as f64 / steps as f64),
|
||||||
|
};
|
||||||
|
self.evaluate(tvalue)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an approximation of the length of the bezier curve.
|
/// Return an approximation of the length of the bezier curve.
|
||||||
|
|
@ -97,7 +100,7 @@ impl Bezier {
|
||||||
|
|
||||||
// We will use an approximate approach where we split the curve into many subdivisions
|
// We will use an approximate approach where we split the curve into many subdivisions
|
||||||
// and calculate the euclidean distance between the two endpoints of the subdivision
|
// and calculate the euclidean distance between the two endpoints of the subdivision
|
||||||
let lookup_table = self.compute_lookup_table(Some(num_subdivisions.unwrap_or(DEFAULT_LENGTH_SUBDIVISIONS)));
|
let lookup_table = self.compute_lookup_table(Some(num_subdivisions.unwrap_or(DEFAULT_LENGTH_SUBDIVISIONS)), Some(TValueType::Parametric));
|
||||||
let mut approx_curve_length = 0.;
|
let mut approx_curve_length = 0.;
|
||||||
let mut previous_point = lookup_table[0];
|
let mut previous_point = lookup_table[0];
|
||||||
// Calculate approximate distance between subdivision
|
// Calculate approximate distance between subdivision
|
||||||
|
|
@ -114,9 +117,10 @@ impl Bezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the parametric `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.
|
/// Uses a searching algorithm akin to binary search that can be customized using the optional [ProjectionOptions] struct.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#bezier/project/solo" title="Project Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#bezier/project/solo" title="Project Demo"></iframe>
|
||||||
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> f64 {
|
pub fn project(&self, point: DVec2, options: Option<ProjectionOptions>) -> f64 {
|
||||||
|
let options = options.unwrap_or_default();
|
||||||
let ProjectionOptions {
|
let ProjectionOptions {
|
||||||
lut_size,
|
lut_size,
|
||||||
convergence_epsilon,
|
convergence_epsilon,
|
||||||
|
|
@ -126,7 +130,7 @@ impl Bezier {
|
||||||
|
|
||||||
// TODO: Consider optimizations from precomputing useful values, or using the GPU
|
// TODO: Consider optimizations from precomputing useful values, or using the GPU
|
||||||
// First find the closest point from the results of a lookup table
|
// First find the closest point from the results of a lookup table
|
||||||
let lut = self.compute_lookup_table(Some(lut_size));
|
let lut = self.compute_lookup_table(Some(lut_size), Some(TValueType::Parametric));
|
||||||
let (minimum_position, minimum_distance) = utils::get_closest_point_in_lut(&lut, point);
|
let (minimum_position, minimum_distance) = utils::get_closest_point_in_lut(&lut, point);
|
||||||
|
|
||||||
// Get the t values to the left and right of the closest result in the lookup table
|
// Get the t values to the left and right of the closest result in the lookup table
|
||||||
|
|
@ -228,11 +232,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compute_lookup_table() {
|
fn test_compute_lookup_table() {
|
||||||
let bezier1 = Bezier::from_quadratic_coordinates(10., 10., 30., 30., 50., 10.);
|
let bezier1 = Bezier::from_quadratic_coordinates(10., 10., 30., 30., 50., 10.);
|
||||||
let lookup_table1 = bezier1.compute_lookup_table(Some(2));
|
let lookup_table1 = bezier1.compute_lookup_table(Some(2), Some(TValueType::Parametric));
|
||||||
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(TValue::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 bezier2 = Bezier::from_cubic_coordinates(10., 10., 30., 30., 70., 70., 90., 10.);
|
||||||
let lookup_table2 = bezier2.compute_lookup_table(Some(4));
|
let lookup_table2 = bezier2.compute_lookup_table(Some(4), Some(TValueType::Parametric));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lookup_table2,
|
lookup_table2,
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -264,13 +268,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_project() {
|
fn test_project() {
|
||||||
let project_options = ProjectionOptions::default();
|
|
||||||
|
|
||||||
let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.);
|
let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.);
|
||||||
assert_eq!(bezier1.project(DVec2::ZERO, project_options), 0.);
|
assert_eq!(bezier1.project(DVec2::ZERO, None), 0.);
|
||||||
assert_eq!(bezier1.project(DVec2::new(100., 100.), project_options), 1.);
|
assert_eq!(bezier1.project(DVec2::new(100., 100.), None), 1.);
|
||||||
|
|
||||||
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
||||||
assert_eq!(bezier2.project(DVec2::new(100., 0.), project_options), 0.);
|
assert_eq!(bezier2.project(DVec2::new(100., 0.), None), 0.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,12 +315,12 @@ impl Bezier {
|
||||||
BezierHandles::Linear => Bezier::from_linear_dvec2(transformed_start, transformed_end),
|
BezierHandles::Linear => Bezier::from_linear_dvec2(transformed_start, transformed_end),
|
||||||
BezierHandles::Quadratic { handle: _ } => unreachable!(),
|
BezierHandles::Quadratic { handle: _ } => unreachable!(),
|
||||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||||
let handle_start_closest_t = intermediate.project(handle_start, ProjectionOptions::default());
|
let handle_start_closest_t = intermediate.project(handle_start, None);
|
||||||
let handle_start_scale_distance = (1. - handle_start_closest_t) * start_distance + handle_start_closest_t * end_distance;
|
let handle_start_scale_distance = (1. - handle_start_closest_t) * start_distance + handle_start_closest_t * end_distance;
|
||||||
let transformed_handle_start =
|
let transformed_handle_start =
|
||||||
utils::scale_point_from_direction_vector(handle_start, intermediate.normal(TValue::Parametric(handle_start_closest_t)), false, handle_start_scale_distance);
|
utils::scale_point_from_direction_vector(handle_start, intermediate.normal(TValue::Parametric(handle_start_closest_t)), false, handle_start_scale_distance);
|
||||||
|
|
||||||
let handle_end_closest_t = intermediate.project(handle_start, ProjectionOptions::default());
|
let handle_end_closest_t = intermediate.project(handle_start, None);
|
||||||
let handle_end_scale_distance = (1. - handle_end_closest_t) * start_distance + handle_end_closest_t * end_distance;
|
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, intermediate.normal(TValue::Parametric(handle_end_closest_t)), false, handle_end_scale_distance);
|
let transformed_handle_end = utils::scale_point_from_direction_vector(handle_end, intermediate.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)
|
Bezier::from_cubic_dvec2(transformed_start, transformed_handle_start, transformed_handle_end, transformed_end)
|
||||||
|
|
@ -810,7 +810,7 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
let offset_point = offset_segment.evaluate(TValue::Parametric(*t));
|
let offset_point = offset_segment.evaluate(TValue::Parametric(*t));
|
||||||
let closest_point_t = bezier.project(offset_point, ProjectionOptions::default());
|
let closest_point_t = bezier.project(offset_point, None);
|
||||||
let closest_point = bezier.evaluate(TValue::Parametric(closest_point_t));
|
let closest_point = bezier.evaluate(TValue::Parametric(closest_point_t));
|
||||||
let actual_distance = offset_point.distance(closest_point);
|
let actual_distance = offset_point.distance(closest_point);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ mod utils;
|
||||||
|
|
||||||
pub use bezier::*;
|
pub use bezier::*;
|
||||||
pub use subpath::*;
|
pub use subpath::*;
|
||||||
pub use utils::{Cap, Join, SubpathTValue, TValue};
|
pub use utils::{Cap, Join, SubpathTValue, TValue, TValueType};
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,29 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::consts::DEFAULT_EUCLIDEAN_ERROR_BOUND;
|
use crate::consts::{DEFAULT_EUCLIDEAN_ERROR_BOUND, DEFAULT_LUT_STEP_SIZE};
|
||||||
use crate::utils::{SubpathTValue, TValue};
|
use crate::utils::{SubpathTValue, TValue, TValueType};
|
||||||
use crate::ProjectionOptions;
|
use crate::ProjectionOptions;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||||
|
/// Return a selection of equidistant points on the bezier curve.
|
||||||
|
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||||
|
/// <iframe frameBorder="0" width="100%" height="375px" src="https://graphite.rs/bezier-rs-demos#subpath/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
||||||
|
pub fn compute_lookup_table(&self, steps: Option<usize>, tvalue_type: Option<TValueType>) -> Vec<DVec2> {
|
||||||
|
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
||||||
|
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric);
|
||||||
|
|
||||||
|
(0..=steps)
|
||||||
|
.map(|t| {
|
||||||
|
let tvalue = match tvalue_type {
|
||||||
|
TValueType::Parametric => SubpathTValue::GlobalParametric(t as f64 / steps as f64),
|
||||||
|
TValueType::Euclidean => SubpathTValue::GlobalEuclidean(t as f64 / steps as f64),
|
||||||
|
};
|
||||||
|
self.evaluate(tvalue)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`.
|
/// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`.
|
||||||
/// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`.
|
/// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/length/solo" title="Length Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/length/solo" title="Length Demo"></iframe>
|
||||||
|
|
@ -80,7 +98,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||||
/// Returns the segment index and `t` value that corresponds to the closest point on the curve to the provided point.
|
/// Returns the segment index and `t` value that corresponds to the closest point on the curve to the provided point.
|
||||||
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
|
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/project/solo" title="Project Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/bezier-rs-demos#subpath/project/solo" title="Project Demo"></iframe>
|
||||||
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> Option<(usize, f64)> {
|
pub fn project(&self, point: DVec2, options: Option<ProjectionOptions>) -> Option<(usize, f64)> {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ pub enum TValue {
|
||||||
EuclideanWithinError { t: f64, error: f64 },
|
EuclideanWithinError { t: f64, error: f64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub enum TValueType {
|
||||||
|
Parametric,
|
||||||
|
Euclidean,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum SubpathTValue {
|
pub enum SubpathTValue {
|
||||||
Parametric { segment_index: usize, t: f64 },
|
Parametric { segment_index: usize, t: f64 },
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,11 @@ const bezierFeatures = {
|
||||||
},
|
},
|
||||||
"lookup-table": {
|
"lookup-table": {
|
||||||
name: "Lookup Table",
|
name: "Lookup Table",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined): string => bezier.compute_lookup_table(options.steps, BEZIER_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
demoOptions: {
|
demoOptions: {
|
||||||
Quadratic: {
|
Quadratic: {
|
||||||
inputOptions: [
|
inputOptions: [
|
||||||
|
bezierTValueVariantOptions,
|
||||||
{
|
{
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 15,
|
max: 15,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,20 @@ const subpathFeatures = {
|
||||||
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.evaluate(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.evaluate(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
inputOptions: [subpathTValueVariantOptions, tSliderOptions],
|
||||||
},
|
},
|
||||||
|
"lookup-table": {
|
||||||
|
name: "Lookup Table",
|
||||||
|
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined): string => subpath.compute_lookup_table(options.steps, SUBPATH_T_VALUE_VARIANTS[options.TVariant]),
|
||||||
|
inputOptions: [
|
||||||
|
subpathTValueVariantOptions,
|
||||||
|
{
|
||||||
|
min: 2,
|
||||||
|
max: 30,
|
||||||
|
step: 1,
|
||||||
|
default: 5,
|
||||||
|
variable: "steps",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
project: {
|
project: {
|
||||||
name: "Project",
|
name: "Project",
|
||||||
callback: (subpath: WasmSubpathInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
|
callback: (subpath: WasmSubpathInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::svg_drawing::*;
|
use crate::svg_drawing::*;
|
||||||
use crate::utils::parse_cap;
|
use crate::utils::parse_cap;
|
||||||
|
|
||||||
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, Identifier, ProjectionOptions, TValue};
|
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, Identifier, TValue, TValueType};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
@ -156,9 +156,14 @@ impl WasmBezier {
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_lookup_table(&self, steps: usize) -> String {
|
pub fn compute_lookup_table(&self, steps: usize, t_variant: String) -> String {
|
||||||
let bezier = self.get_bezier_path();
|
let bezier = self.get_bezier_path();
|
||||||
let table_values: Vec<DVec2> = self.0.compute_lookup_table(Some(steps));
|
let tvalue_type = match t_variant.as_str() {
|
||||||
|
"Parametric" => TValueType::Parametric,
|
||||||
|
"Euclidean" => TValueType::Euclidean,
|
||||||
|
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||||
|
};
|
||||||
|
let table_values: Vec<DVec2> = self.0.compute_lookup_table(Some(steps), Some(tvalue_type));
|
||||||
let circles: String = table_values
|
let circles: String = table_values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|point| draw_circle(*point, 3., RED, 1.5, WHITE))
|
.map(|point| draw_circle(*point, 3., RED, 1.5, WHITE))
|
||||||
|
|
@ -287,7 +292,7 @@ impl WasmBezier {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project(&self, x: f64, y: f64) -> String {
|
pub fn project(&self, x: f64, y: f64) -> String {
|
||||||
let projected_t_value = self.0.project(DVec2::new(x, y), ProjectionOptions::default());
|
let projected_t_value = self.0.project(DVec2::new(x, y), None);
|
||||||
let projected_point = self.0.evaluate(TValue::Parametric(projected_t_value));
|
let projected_point = self.0.evaluate(TValue::Parametric(projected_t_value));
|
||||||
|
|
||||||
let bezier = self.get_bezier_path();
|
let bezier = self.get_bezier_path();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::svg_drawing::*;
|
use crate::svg_drawing::*;
|
||||||
use crate::utils::{parse_cap, parse_join};
|
use crate::utils::{parse_cap, parse_join};
|
||||||
|
|
||||||
use bezier_rs::{Bezier, ManipulatorGroup, ProjectionOptions, Subpath, SubpathTValue};
|
use bezier_rs::{Bezier, ManipulatorGroup, Subpath, SubpathTValue, TValueType};
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
@ -99,6 +99,22 @@ impl WasmSubpath {
|
||||||
wrap_svg_tag(format!("{}{}", self.to_default_svg(), point_text))
|
wrap_svg_tag(format!("{}{}", self.to_default_svg(), point_text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compute_lookup_table(&self, steps: usize, t_variant: String) -> String {
|
||||||
|
let subpath = self.to_default_svg();
|
||||||
|
let tvalue_type = match t_variant.as_str() {
|
||||||
|
"GlobalParametric" => TValueType::Parametric,
|
||||||
|
"GlobalEuclidean" => TValueType::Euclidean,
|
||||||
|
_ => panic!("Unexpected TValue string: '{}'", t_variant),
|
||||||
|
};
|
||||||
|
let table_values: Vec<DVec2> = self.0.compute_lookup_table(Some(steps), Some(tvalue_type));
|
||||||
|
let circles: String = table_values
|
||||||
|
.iter()
|
||||||
|
.map(|point| draw_circle(*point, 3., RED, 1.5, WHITE))
|
||||||
|
.fold("".to_string(), |acc, circle| acc + &circle);
|
||||||
|
let content = format!("{subpath}{circles}");
|
||||||
|
wrap_svg_tag(content)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tangent(&self, t: f64, t_variant: String) -> String {
|
pub fn tangent(&self, t: f64, t_variant: String) -> String {
|
||||||
let t = parse_t_variant(&t_variant, t);
|
let t = parse_t_variant(&t_variant, t);
|
||||||
|
|
||||||
|
|
@ -204,7 +220,7 @@ impl WasmSubpath {
|
||||||
}
|
}
|
||||||
|
|
||||||
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), None).unwrap();
|
||||||
let projected_point = self.0.evaluate(SubpathTValue::Parametric { segment_index, t: projected_t });
|
let projected_point = self.0.evaluate(SubpathTValue::Parametric { segment_index, t: projected_t });
|
||||||
|
|
||||||
let subpath_svg = self.to_default_svg();
|
let subpath_svg = self.to_default_svg();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ pub const WHITE: &str = "white";
|
||||||
pub const GRAY: &str = "gray";
|
pub const GRAY: &str = "gray";
|
||||||
pub const RED: &str = "red";
|
pub const RED: &str = "red";
|
||||||
pub const ORANGE: &str = "orange";
|
pub const ORANGE: &str = "orange";
|
||||||
pub const PINK: &str = "pink";
|
// pub const PINK: &str = "pink";
|
||||||
pub const GREEN: &str = "green";
|
pub const GREEN: &str = "green";
|
||||||
pub const NONE: &str = "none";
|
pub const NONE: &str = "none";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue