Skip to content

Commit b84e647

Browse files
Androxiumhannahli2010RobNadalKeavon
committed
Implement arcs for Bezier math library (#731)
* added arcs impl Co-authored-by: Hannah Li <[email protected]> Co-authored-by: Rob Nadal <[email protected]> * fixed arc drawing, todo - fix linear check Co-authored-by: Hannah Li <[email protected]> * fixed linear bug + added comments and tests Co-authored-by: Hannah Li <[email protected]> * added max iteration guard + made params optional + added impl todo * Add functionality to get arcs between extrema * Add ArcsOptions to manage optional parameters of the arcs function * added slider to toggle between arcs impl Co-authored-by: Rob Nadal <[email protected]> * Remove unused types * address some comments * added rustdoc for CircularArc struct * Extract duplicate code into helper, remove loop labels, use window function * Make JsValue handling consistent in WasmBezier and add comments for the underlying type * Add enum for MaximizeArcs Auto/On/Off functionality * Change Auto to Automatic * fix errors from resolving merge conflict * fixed error from resolving merge conflicts * fixed formatting * address comments * Small fix * Add some missing comments * address comments * rename variable * Use unit to show maximize_arcs values * Change i32 to usize and other minor adjustments * Change computation for middle t values * Remove tsconfig * Fix more usize number handling Co-authored-by: Hannah Li <[email protected]> Co-authored-by: Rob Nadal <[email protected]> Co-authored-by: Keavon Chambers <[email protected]>
1 parent 0f88055 commit b84e647

File tree

13 files changed

+582
-103
lines changed

13 files changed

+582
-103
lines changed

bezier-rs/docs/interactive-docs/src/App.vue

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
<script lang="ts">
2626
import { defineComponent, markRaw } from "vue";
2727
28-
import { drawBezier, drawBezierHelper, drawCircle, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
29-
import { BezierCurveType, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
28+
import { drawBezier, drawBezierHelper, drawCircle, drawCircleSector, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
29+
import { BezierCurveType, CircleSector, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
3030
3131
import ExamplePane from "@/components/ExamplePane.vue";
3232
import SliderExample from "@/components/SliderExample.vue";
@@ -115,10 +115,10 @@ export default defineComponent({
115115
{
116116
name: "Lookup Table",
117117
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
118-
const lookupPoints = bezier.compute_lookup_table(options.steps);
119-
lookupPoints.forEach((serializedPoint, index) => {
118+
const lookupPoints: Point[] = JSON.parse(bezier.compute_lookup_table(options.steps));
119+
lookupPoints.forEach((point, index) => {
120120
if (index !== 0 && index !== lookupPoints.length - 1) {
121-
drawPoint(getContextFromCanvas(canvas), JSON.parse(serializedPoint), 3, COLORS.NON_INTERACTIVE.STROKE_1);
121+
drawPoint(getContextFromCanvas(canvas), point, 3, COLORS.NON_INTERACTIVE.STROKE_1);
122122
}
123123
});
124124
},
@@ -142,7 +142,7 @@ export default defineComponent({
142142
143143
const derivativeBezier = bezier.derivative();
144144
if (derivativeBezier) {
145-
const points: Point[] = derivativeBezier.get_points().map((p) => JSON.parse(p));
145+
const points: Point[] = JSON.parse(derivativeBezier.get_points());
146146
if (points.length === 2) {
147147
drawLine(context, points[0], points[1], COLORS.NON_INTERACTIVE.STROKE_1);
148148
} else {
@@ -356,10 +356,7 @@ export default defineComponent({
356356
name: "Rotate",
357357
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
358358
const context = getContextFromCanvas(canvas);
359-
const rotatedBezier = bezier
360-
.rotate(options.angle * Math.PI)
361-
.get_points()
362-
.map((p) => JSON.parse(p));
359+
const rotatedBezier = JSON.parse(bezier.rotate(options.angle * Math.PI).get_points());
363360
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
364361
},
365362
template: markRaw(SliderExample),
@@ -496,6 +493,57 @@ export default defineComponent({
496493
});
497494
},
498495
},
496+
{
497+
name: "Arcs",
498+
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
499+
const context = getContextFromCanvas(canvas);
500+
const arcs: CircleSector[] = JSON.parse(bezier.arcs(options.error, options.max_iterations, options.strategy));
501+
arcs.forEach((circleSector, index) => {
502+
drawCircleSector(context, circleSector, `hsl(${40 * index}, 100%, 50%, 75%)`, `hsl(${40 * index}, 100%, 50%, 37.5%)`);
503+
});
504+
},
505+
template: markRaw(SliderExample),
506+
templateOptions: {
507+
sliders: [
508+
{
509+
variable: "strategy",
510+
min: 0,
511+
max: 2,
512+
step: 1,
513+
default: 0,
514+
unit: [": Automatic", ": FavorLargerArcs", ": FavorCorrectness"],
515+
},
516+
{
517+
variable: "error",
518+
min: 0.05,
519+
max: 1,
520+
step: 0.05,
521+
default: 0.5,
522+
},
523+
{
524+
variable: "max_iterations",
525+
min: 50,
526+
max: 200,
527+
step: 1,
528+
default: 100,
529+
},
530+
],
531+
},
532+
curveDegrees: new Set([BezierCurveType.Quadratic, BezierCurveType.Cubic]),
533+
customPoints: {
534+
[BezierCurveType.Quadratic]: [
535+
[50, 50],
536+
[85, 65],
537+
[100, 100],
538+
],
539+
[BezierCurveType.Cubic]: [
540+
[160, 180],
541+
[170, 10],
542+
[30, 90],
543+
[180, 160],
544+
],
545+
},
546+
},
499547
{
500548
name: "Offset",
501549
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {

bezier-rs/docs/interactive-docs/src/components/BezierDrawing.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,14 @@ class BezierDrawing {
3535
this.callback = callback;
3636
this.options = options;
3737
this.createThroughPoints = createThroughPoints;
38-
this.points = bezier
39-
.get_points()
40-
.map((p) => JSON.parse(p))
41-
.map((p, i, points) => ({
42-
x: p.x,
43-
y: p.y,
44-
r: getPointSizeByIndex(i, points.length),
45-
selected: false,
46-
manipulator: MANIPULATOR_KEYS_FROM_BEZIER_TYPE[points.length][i],
47-
}));
38+
const bezierPoints: Point[] = JSON.parse(bezier.get_points());
39+
this.points = bezierPoints.map((p, i, points) => ({
40+
x: p.x,
41+
y: p.y,
42+
r: getPointSizeByIndex(i, points.length),
43+
selected: false,
44+
manipulator: MANIPULATOR_KEYS_FROM_BEZIER_TYPE[points.length][i],
45+
}));
4846

4947
if (this.createThroughPoints && this.points.length === 4) {
5048
// Use the first handler as the middle point
@@ -122,22 +120,22 @@ class BezierDrawing {
122120

123121
// For the create through points cases, we store a bezier where the handle is actually the point that the curve should pass through
124122
// This is so that we can re-use the drag and drop logic, while simply drawing the desired bezier instead
125-
const actualBezierPointLength = this.bezier.get_points().length;
123+
const actualBezierPointLength = JSON.parse(this.bezier.get_points()).length;
126124
let pointsToDraw = this.points;
127125

128126
let styleConfig: Partial<BezierStyleConfig> = {
129127
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
130128
};
131129
let dragIndex = this.dragIndex;
132130
if (this.createThroughPoints) {
133-
let serializedPoints;
131+
let bezierThroughPoints;
134132
const pointList = this.points.map((p) => [p.x, p.y]);
135133
if (actualBezierPointLength === 3) {
136-
serializedPoints = WasmBezier.quadratic_through_points(pointList, this.options.t);
134+
bezierThroughPoints = WasmBezier.quadratic_through_points(pointList, this.options.t);
137135
} else {
138-
serializedPoints = WasmBezier.cubic_through_points(pointList, this.options.t, this.options["midpoint separation"]);
136+
bezierThroughPoints = WasmBezier.cubic_through_points(pointList, this.options.t, this.options["midpoint separation"]);
139137
}
140-
pointsToDraw = serializedPoints.get_points().map((p) => JSON.parse(p));
138+
pointsToDraw = JSON.parse(bezierThroughPoints.get_points());
141139
if (this.dragIndex === 1) {
142140
// Do not propagate dragIndex when the the non-endpoint is moved
143141
dragIndex = null;

bezier-rs/docs/interactive-docs/src/components/SliderExample.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div>
33
<Example :title="title" :bezier="bezier" :callback="callback" :options="sliderData" :createThroughPoints="createThroughPoints" />
44
<div v-for="(slider, index) in templateOptions.sliders" :key="index">
5-
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ sliderUnits[slider.variable] }}</div>
5+
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
66
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
77
</div>
88
</div>
@@ -45,6 +45,9 @@ export default defineComponent({
4545
components: {
4646
Example,
4747
},
48+
methods: {
49+
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
50+
},
4851
});
4952
</script>
5053

bezier-rs/docs/interactive-docs/src/utils/drawing.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BezierStyleConfig, Point, WasmBezierInstance } from "@/utils/types";
1+
import { BezierStyleConfig, CircleSector, Point, WasmBezierInstance } from "@/utils/types";
22

33
const HANDLE_RADIUS_FACTOR = 2 / 3;
44
const DEFAULT_ENDPOINT_RADIUS = 5;
@@ -81,8 +81,22 @@ export const drawCircle = (ctx: CanvasRenderingContext2D, point: Point, radius:
8181
ctx.stroke();
8282
};
8383

84+
export const drawCircleSector = (ctx: CanvasRenderingContext2D, circleSector: CircleSector, strokeColor = COLORS.INTERACTIVE.STROKE_1, fillColor = COLORS.NON_INTERACTIVE.STROKE_1): void => {
85+
ctx.strokeStyle = strokeColor;
86+
ctx.fillStyle = fillColor;
87+
ctx.lineWidth = 2;
88+
89+
const { center, radius, startAngle, endAngle } = circleSector;
90+
ctx.beginPath();
91+
ctx.moveTo(center.x, center.y);
92+
ctx.arc(center.x, center.y, radius, startAngle, endAngle);
93+
ctx.lineTo(center.x, center.y);
94+
ctx.closePath();
95+
ctx.fill();
96+
};
97+
8498
export const drawBezierHelper = (ctx: CanvasRenderingContext2D, bezier: WasmBezierInstance, bezierStyleConfig: Partial<BezierStyleConfig> = {}): void => {
85-
const points = bezier.get_points().map((p: string) => JSON.parse(p));
99+
const points = JSON.parse(bezier.get_points());
86100
drawBezier(ctx, points, null, bezierStyleConfig);
87101
};
88102

bezier-rs/docs/interactive-docs/src/utils/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type SliderOption = {
2323
step: number;
2424
default: number;
2525
variable: string;
26-
unit?: string;
26+
unit?: string | string[];
2727
};
2828

2929
export type TemplateOption = {
@@ -46,3 +46,10 @@ export type BezierStyleConfig = {
4646
radius: number;
4747
drawHandles: boolean;
4848
};
49+
50+
export type CircleSector = {
51+
center: Point;
52+
radius: number;
53+
startAngle: number;
54+
endAngle: number;
55+
};

0 commit comments

Comments
 (0)