From 004c2aeff643e998487233ee6902b5b1d7a11cd2 Mon Sep 17 00:00:00 2001 From: Jackie Chen <46739769+jackiechen73@users.noreply.github.com> Date: Wed, 27 Jul 2022 00:35:43 -0400 Subject: [PATCH] Implement bezier library's De Casteljau points function (#715) * pre-rebase * broken wasm * hull lines * update rust lib description * update comments, handle match statement better, pass Point type to vue * cleanup * add linear case * More idiomatic code * Further simplifications to the algorithm and removal of more heap allocations * Rename to de_casteljau_points and use colors for the iterations * Small comment changes * Improve colors Co-authored-by: Jackie Chen Co-authored-by: Keavon Chambers Co-authored-by: Hannah Li --- bezier-rs/docs/interactive-docs/src/App.vue | 23 ++++++++++++++++++ .../docs/interactive-docs/wasm/src/lib.rs | 10 ++++++++ bezier-rs/lib/src/lib.rs | 24 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/bezier-rs/docs/interactive-docs/src/App.vue b/bezier-rs/docs/interactive-docs/src/App.vue index 307d1803c..ed1750ad8 100644 --- a/bezier-rs/docs/interactive-docs/src/App.vue +++ b/bezier-rs/docs/interactive-docs/src/App.vue @@ -281,6 +281,29 @@ export default defineComponent({ ], }, }, + { + name: "De Casteljau Points", + callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record): void => { + const hullPoints: Point[][] = JSON.parse(bezier.de_casteljau_points(options.t)); + hullPoints.reverse().forEach((iteration: Point[], iterationIndex) => { + const colorLight = `hsl(${90 * iterationIndex}, 100%, 50%)`; + + iteration.forEach((point: Point, index) => { + // Skip the anchor and handle points which are already drawn in black + if (iterationIndex !== hullPoints.length - 1) { + drawPoint(getContextFromCanvas(canvas), point, 4, colorLight); + } + + if (index !== 0) { + const prevPoint: Point = iteration[index - 1]; + drawLine(getContextFromCanvas(canvas), point, prevPoint, colorLight); + } + }); + }); + }, + template: markRaw(SliderExample), + templateOptions: { sliders: [tSliderOptions] }, + }, { name: "Rotate", callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record): void => { diff --git a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs index 1b0445f24..d690a0fbf 100644 --- a/bezier-rs/docs/interactive-docs/wasm/src/lib.rs +++ b/bezier-rs/docs/interactive-docs/wasm/src/lib.rs @@ -138,4 +138,14 @@ impl WasmBezier { let bezier_points: Vec> = self.0.reduce(None).into_iter().map(bezier_to_points).collect(); to_js_value(bezier_points) } + + pub fn de_casteljau_points(&self, t: f64) -> JsValue { + let hull = self + .0 + .de_casteljau_points(t) + .iter() + .map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::>()) + .collect::>>(); + to_js_value(hull) + } } diff --git a/bezier-rs/lib/src/lib.rs b/bezier-rs/lib/src/lib.rs index 71e88acee..1073bb1fd 100644 --- a/bezier-rs/lib/src/lib.rs +++ b/bezier-rs/lib/src/lib.rs @@ -631,6 +631,30 @@ impl Bezier { .collect::>() } + /// Returns a list of lists of points representing the De Casteljau points for all iterations at the point corresponding to `t` using De Casteljau's algorithm. + /// The `i`th element of the list represents the set of points in the `i`th iteration. + /// More information on the algorithm can be found in the [De Casteljau section](https://pomax.github.io/bezierinfo/#decasteljau) in Pomax's primer. + pub fn de_casteljau_points(&self, t: f64) -> Vec> { + let bezier_points = match self.handles { + BezierHandles::Linear => vec![self.start, self.end], + BezierHandles::Quadratic { handle } => vec![self.start, handle, self.end], + BezierHandles::Cubic { handle_start, handle_end } => vec![self.start, handle_start, handle_end, self.end], + }; + let mut de_casteljau_points = vec![bezier_points]; + let mut current_points = de_casteljau_points.last().unwrap(); + + // Iterate until one point is left, that point will be equal to `evaluate(t)` + while current_points.len() > 1 { + // Map from every adjacent pair of points to their respective midpoints, which decrements by 1 the number of points for the next iteration + let next_points: Vec = current_points.as_slice().windows(2).map(|pair| DVec2::lerp(pair[0], pair[1], t)).collect(); + de_casteljau_points.push(next_points); + + current_points = de_casteljau_points.last().unwrap(); + } + + de_casteljau_points + } + /// Determine if it is possible to scale the given curve, using the following conditions: /// 1. All the handles are located on a single side of the curve. /// 2. The on-curve point for `t = 0.5` must occur roughly in the center of the polygon defined by the curve's endpoint normals.