Implement curvature function in Bezier math library (#725)

* bezier curvature

* change comment

Co-authored-by: Jackie Chen <jackiechen73>
This commit is contained in:
Jackie Chen 2022-07-29 21:58:16 -04:00 committed by Keavon Chambers
parent 30e5d3c8ec
commit 8c1e6455eb
4 changed files with 53 additions and 1 deletions

View File

@ -25,7 +25,7 @@
<script lang="ts">
import { defineComponent, markRaw } from "vue";
import { drawBezier, drawBezierHelper, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
import { drawBezier, drawBezierHelper, drawCircle, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
import { BezierCurveType, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
import ExamplePane from "@/components/ExamplePane.vue";
@ -205,6 +205,26 @@ export default defineComponent({
template: markRaw(SliderExample),
templateOptions: { sliders: [tSliderOptions] },
},
{
name: "Curvature",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
const context = getContextFromCanvas(canvas);
const point = JSON.parse(bezier.evaluate(options.t));
const normal = JSON.parse(bezier.normal(options.t));
const curvature = bezier.curvature(options.t);
const radius = 1 / curvature;
const curvatureCenter = { x: point.x + normal.x * radius, y: point.y + normal.y * radius };
drawCircle(context, curvatureCenter, Math.abs(radius), COLORS.NON_INTERACTIVE.STROKE_1);
drawLine(context, point, curvatureCenter, COLORS.NON_INTERACTIVE.STROKE_1);
drawPoint(context, point, 3, COLORS.NON_INTERACTIVE.STROKE_1);
drawPoint(context, curvatureCenter, 3, COLORS.NON_INTERACTIVE.STROKE_1);
},
curveDegrees: new Set([BezierCurveType.Quadratic, BezierCurveType.Cubic]),
template: markRaw(SliderExample),
templateOptions: { sliders: [tSliderOptions] },
},
{
name: "Split",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {

View File

@ -73,6 +73,14 @@ export const drawCurve = (ctx: CanvasRenderingContext2D, points: Point[], stroke
ctx.stroke();
};
export const drawCircle = (ctx: CanvasRenderingContext2D, point: Point, radius: number, strokeColor = COLORS.INTERACTIVE.STROKE_1): void => {
ctx.strokeStyle = strokeColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI, false);
ctx.stroke();
};
export const drawBezierHelper = (ctx: CanvasRenderingContext2D, bezier: WasmBezierInstance, bezierStyleConfig: Partial<BezierStyleConfig> = {}): void => {
const points = bezier.get_points().map((p: string) => JSON.parse(p));
drawBezier(ctx, points, null, bezierStyleConfig);

View File

@ -166,4 +166,8 @@ impl WasmBezier {
let bezier_points: Vec<Vec<Point>> = self.0.offset(distance).into_iter().map(bezier_to_points).collect();
to_js_value(bezier_points)
}
pub fn curvature(&self, t: f64) -> f64 {
self.0.curvature(t)
}
}

View File

@ -394,6 +394,26 @@ impl Bezier {
self.tangent(t).perp()
}
/// Returns the curvature, a scalar value for the derivative at the given `t`-value along the curve.
/// Curvature is 1 over the radius of a circle with an equivalent derivative.
pub fn curvature(&self, t: f64) -> f64 {
let (d, dd) = match &self.derivative() {
Some(first_derivative) => match first_derivative.derivative() {
Some(second_derivative) => (first_derivative.evaluate(t), second_derivative.evaluate(t)),
None => (first_derivative.evaluate(t), first_derivative.end - first_derivative.start),
},
None => (self.end - self.start, DVec2::new(0., 0.)),
};
let numerator = d.x * dd.y - d.y * dd.x;
let denominator = (d.x.powf(2.) + d.y.powf(2.)).powf(1.5);
if denominator == 0. {
0.
} else {
numerator / denominator
}
}
/// Returns the pair of Bezier curves that result from splitting the original curve at the point corresponding to `t`.
pub fn split(&self, t: f64) -> [Bezier; 2] {
let split_point = self.evaluate(t);