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 <jackiechen73>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Hannah Li <hannahli2010@gmail.com>
This commit is contained in:
Jackie Chen 2022-07-27 00:35:43 -04:00 committed by Keavon Chambers
parent dc0b38750c
commit 004c2aeff6
3 changed files with 57 additions and 0 deletions

View file

@ -281,6 +281,29 @@ export default defineComponent({
],
},
},
{
name: "De Casteljau Points",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): 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<string, number>): void => {

View file

@ -138,4 +138,14 @@ impl WasmBezier {
let bezier_points: Vec<Vec<Point>> = 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::<Vec<Point>>())
.collect::<Vec<Vec<Point>>>();
to_js_value(hull)
}
}

View file

@ -631,6 +631,30 @@ impl Bezier {
.collect::<Vec<DVec2>>()
}
/// 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<Vec<DVec2>> {
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<DVec2> = 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.