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:
parent
dc0b38750c
commit
004c2aeff6
|
|
@ -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",
|
name: "Rotate",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
|
|
|
||||||
|
|
@ -138,4 +138,14 @@ impl WasmBezier {
|
||||||
let bezier_points: Vec<Vec<Point>> = self.0.reduce(None).into_iter().map(bezier_to_points).collect();
|
let bezier_points: Vec<Vec<Point>> = self.0.reduce(None).into_iter().map(bezier_to_points).collect();
|
||||||
to_js_value(bezier_points)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -631,6 +631,30 @@ impl Bezier {
|
||||||
.collect::<Vec<DVec2>>()
|
.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:
|
/// 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.
|
/// 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.
|
/// 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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue