diff --git a/bezier-rs/docs/interactive-docs/src/App.vue b/bezier-rs/docs/interactive-docs/src/App.vue index a69358a08f..11288672d4 100644 --- a/bezier-rs/docs/interactive-docs/src/App.vue +++ b/bezier-rs/docs/interactive-docs/src/App.vue @@ -3,19 +3,21 @@

Bezier-rs Interactive Documentation

This is the interactive documentation for the bezier-rs library. Click and drag on the endpoints of the example curves to visualize the various Bezier utilities and functions.

- +
+
diff --git a/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue b/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue index 958973e37a..1f1b2a2656 100644 --- a/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue +++ b/bezier-rs/docs/interactive-docs/src/components/ExamplePane.vue @@ -1,16 +1,16 @@ + + diff --git a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts index 1a2a2585c9..938d3c55c3 100644 --- a/bezier-rs/docs/interactive-docs/src/utils/drawing.ts +++ b/bezier-rs/docs/interactive-docs/src/utils/drawing.ts @@ -18,9 +18,9 @@ export const drawLine = (ctx: CanvasRenderingContext2D, p1: Point, p2: Point): v ctx.stroke(); }; -export const drawPoint = (ctx: CanvasRenderingContext2D, p: Point): void => { +export const drawPoint = (ctx: CanvasRenderingContext2D, p: Point, stroke = "black"): void => { // Outline the point - ctx.strokeStyle = p.selected ? "blue" : "black"; + ctx.strokeStyle = p.selected ? "blue" : stroke; ctx.lineWidth = p.r / 3; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, 2 * Math.PI, false); diff --git a/bezier-rs/docs/interactive-docs/src/utils/types.ts b/bezier-rs/docs/interactive-docs/src/utils/types.ts index ef05fde1bd..a0e0cc8a59 100644 --- a/bezier-rs/docs/interactive-docs/src/utils/types.ts +++ b/bezier-rs/docs/interactive-docs/src/utils/types.ts @@ -4,7 +4,7 @@ export type WasmBezierInstance = InstanceType; export type WasmBezierKey = keyof WasmBezierInstance; export type WasmBezierMutatorKey = "set_start" | "set_handle1" | "set_handle2" | "set_end"; -export type BezierCallback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance) => void; +export type BezierCallback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: string) => void; export type Point = { x: number; diff --git a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs index 74e015eaae..66323b4a64 100644 --- a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs +++ b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs @@ -14,6 +14,10 @@ pub struct WasmBezier { internal: Bezier, } +pub fn vec_to_point(p: &DVec2) -> JsValue { + JsValue::from_serde(&serde_json::to_string(&Point { x: p[0], y: p[1] }).unwrap()).unwrap() +} + #[wasm_bindgen] impl WasmBezier { /// Expect js_points to be a list of 3 pairs @@ -49,12 +53,7 @@ impl WasmBezier { } pub fn get_points(&self) -> Vec { - self.internal - .get_points() - .iter() - .flatten() - .map(|p| JsValue::from_serde(&serde_json::to_string(&Point { x: p[0], y: p[1] }).unwrap()).unwrap()) - .collect() + self.internal.get_points().iter().flatten().map(vec_to_point).collect() } pub fn to_svg(&self) -> String { @@ -64,4 +63,12 @@ impl WasmBezier { pub fn length(&self) -> f64 { self.internal.length() } + + pub fn compute(&self, t: f64) -> JsValue { + vec_to_point(&self.internal.compute(t)) + } + + pub fn compute_lookup_table(&self, steps: i32) -> Vec { + self.internal.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect() + } } diff --git a/bezier-rs/lib/src/lib.rs b/bezier-rs/lib/src/lib.rs index 8b073111a2..330b9f0adf 100644 --- a/bezier-rs/lib/src/lib.rs +++ b/bezier-rs/lib/src/lib.rs @@ -156,7 +156,9 @@ impl Bezier { /// Calculate the point on the curve based on the t-value provided /// basis code based off of pseudocode found here: https://pomax.github.io/bezierinfo/#explanation - pub fn get_basis(&self, t: f64) -> DVec2 { + pub fn compute(&self, t: f64) -> DVec2 { + assert!((0.0..=1.0).contains(&t)); + let t_squared = t * t; let one_minus_t = 1.0 - t; let squared_one_minus_t = one_minus_t * one_minus_t; @@ -171,6 +173,20 @@ impl Bezier { } } + /// 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 + pub fn compute_lookup_table(&self, steps: Option) -> Vec { + let steps_unwrapped = steps.unwrap_or(10); + let ratio: f64 = 1.0 / (steps_unwrapped as f64); + let mut steps_array = Vec::with_capacity((steps_unwrapped + 1) as usize); + + for t in 0..steps_unwrapped + 1 { + steps_array.push(self.compute(f64::from(t) * ratio)) + } + + steps_array + } + /// Return an approximation of the length of the bezier curve /// code example taken from: https://gamedev.stackexchange.com/questions/5373/moving-ships-between-two-planets-along-a-bezier-missing-some-equations-for-acce/5427#5427 pub fn length(&self) -> f64 { @@ -178,21 +194,18 @@ impl Bezier { // we split the curve into many subdivisions // and calculate the euclidean distance between the two endpoints of the subdivision const SUBDIVISIONS: i32 = 1000; - const RATIO: f64 = 1.0 / (SUBDIVISIONS as f64); - // start_point tracks the starting point of the subdivision - let mut start_point = self.get_basis(0.0); - let mut length_subtotal = 0.0; + let lookup_table = self.compute_lookup_table(Some(SUBDIVISIONS)); + let mut approx_curve_length = 0.0; + let mut prev_point = lookup_table[0]; // calculate approximate distance between subdivision - for subdivision in 1..SUBDIVISIONS + 1 { - // get end point of the subdivision - let end_point = self.get_basis(f64::from(subdivision) * RATIO); + for curr_point in lookup_table.iter().skip(1) { // calculate distance of subdivision - length_subtotal += (start_point - end_point).length(); - // update start_point for next subdivision - start_point = end_point; + approx_curve_length += (*curr_point - prev_point).length(); + // update the prev point + prev_point = *curr_point; } - length_subtotal + approx_curve_length } }