Implement function to find inflection points of a Bezier curve (#712)

* Implement inflection function for bezier-rs

* Swapped to explicit inflection formula

Co-authored-by: Linda Zheng <ll2zheng@uwaterloo.ca>

* Address Rob's comments

* Fix axis align

Co-authored-by: Linda Zheng <linda-zheng@users.noreply.github.com>

* Address Keavon's comments

* Fix linting

Co-authored-by: Robert Nadal <Robnadal44@gmail.com>
Co-authored-by: Hannah Li <hannahli2010@gmail.com>
Co-authored-by: Linda Zheng <linda-zheng@users.noreply.github.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Linda Zheng 2022-07-27 22:52:50 -04:00 committed by Keavon Chambers
parent c05c93c8a2
commit 19483a9a35
3 changed files with 56 additions and 0 deletions

View File

@ -369,6 +369,18 @@ export default defineComponent({
drawLine(context, maxPoint, { x: maxPoint.x, y: minPoint.y }, COLORS.NON_INTERACTIVE.STROKE_1);
},
},
{
name: "Inflections",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
const context = getContextFromCanvas(canvas);
const inflections: number[] = JSON.parse(bezier.inflections());
inflections.forEach((t) => {
const point = JSON.parse(bezier.evaluate(t));
drawPoint(context, point, 4, COLORS.NON_INTERACTIVE.STROKE_1);
});
},
curveDegrees: new Set([BezierCurveType.Cubic]),
},
],
subpathFeatures: [
{

View File

@ -156,4 +156,9 @@ impl WasmBezier {
let bbox_points: [Point; 2] = self.0.bounding_box().map(|p| Point { x: p.x, y: p.y });
to_js_value(bbox_points)
}
pub fn inflections(&self) -> JsValue {
let inflections = self.0.inflections();
to_js_value(inflections)
}
}

View File

@ -793,6 +793,45 @@ impl Bezier {
[endpoints_min, endpoints_max]
}
// TODO: Use an `impl Iterator` return type instead of a `Vec`
/// Returns list of `t`-values representing the inflection points of the curve.
/// The inflection points are defined to be points at which the second derivative of the curve is equal to zero.
pub fn unrestricted_inflections(&self) -> Vec<f64> {
match self.handles {
// There exists no inflection points for linear and quadratic beziers.
BezierHandles::Linear => Vec::new(),
BezierHandles::Quadratic { .. } => Vec::new(),
BezierHandles::Cubic { .. } => {
// Axis align the curve.
let translated_bezier = self.translate(-self.start);
let angle = translated_bezier.end.angle_between(DVec2::new(1., 0.));
let rotated_bezier = translated_bezier.rotate(angle);
if let BezierHandles::Cubic { handle_start, handle_end } = rotated_bezier.handles {
// These formulas and naming conventions follows https://pomax.github.io/bezierinfo/#inflections
let a = handle_end.x * handle_start.y;
let b = rotated_bezier.end.x * handle_start.y;
let c = handle_start.x * handle_end.y;
let d = rotated_bezier.end.x * handle_end.y;
let x = -3. * a + 2. * b + 3. * c - d;
let y = 3. * a - b - 3. * c;
let z = c - a;
let discriminant = y * y - 4. * x * z;
utils::solve_quadratic(discriminant, 2. * x, y, z)
} else {
unreachable!("shouldn't happen")
}
}
}
}
/// Returns list of `t`-values representing the inflection points of the curve.
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
pub fn inflections(&self) -> Vec<f64> {
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::<Vec<f64>>()
}
}
#[cfg(test)]