Bezier-rs: Convert from canvas to svg for constructors (#776)

* Convert constructor to use svg

* Convert the through_points functions to svg

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Hannah Li
2022-09-26 19:14:27 -07:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 8dce2144c4
commit e9cd792635
10 changed files with 308 additions and 57 deletions
@@ -1,7 +1,5 @@
import { WasmBezier } from "@/../wasm/pkg";
import { COLORS, drawBezier, drawPoint, getContextFromCanvas, getPointSizeByIndex } from "@/utils/drawing";
import { BezierCallback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
import { Callback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
// Offset to increase selectable range, used to make points easier to grab
const FUDGE_FACTOR = 3;
@@ -24,13 +22,13 @@ class BezierDrawing {
bezier: WasmBezierInstance;
callback: BezierCallback;
callback: Callback;
options: Record<string, number>;
createThroughPoints: boolean;
constructor(bezier: WasmBezierInstance, callback: BezierCallback, options: Record<string, number>, createThroughPoints = false) {
constructor(bezier: WasmBezierInstance, callback: Callback, options: Record<string, number>, createThroughPoints = false) {
this.bezier = bezier;
this.callback = callback;
this.options = options;
@@ -120,22 +118,13 @@ class BezierDrawing {
// For the create through points cases, we store a bezier where the handle is actually the point that the curve should pass through
// This is so that we can re-use the drag and drop logic, while simply drawing the desired bezier instead
const actualBezierPointLength = JSON.parse(this.bezier.get_points()).length;
let pointsToDraw = this.points;
const pointsToDraw = this.points;
let styleConfig: Partial<BezierStyleConfig> = {
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
};
let dragIndex = this.dragIndex;
if (this.createThroughPoints) {
let bezierThroughPoints;
const pointList = this.points.map((p) => [p.x, p.y]);
if (actualBezierPointLength === 3) {
bezierThroughPoints = WasmBezier.quadratic_through_points(pointList, this.options.t);
} else {
bezierThroughPoints = WasmBezier.cubic_through_points(pointList, this.options.t, this.options["midpoint separation"]);
}
pointsToDraw = JSON.parse(bezierThroughPoints.get_points());
if (this.dragIndex === 1) {
// Do not propagate dragIndex when the the non-endpoint is moved
dragIndex = null;
@@ -0,0 +1,105 @@
<template>
<div>
<h4 class="example-header">{{ title }}</h4>
<figure @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove" class="example-figure" v-html="bezierSVG"></figure>
<div v-for="(slider, index) in sliderOptions" :key="index">
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { WasmBezier } from "@/../wasm/pkg";
import { getConstructorKey, getCurveType } from "@/utils/helpers";
import { BezierCallback, BezierCurveType, WasmBezierManipulatorKey, SliderOption } from "@/utils/types";
const SELECTABLE_RANGE = 10;
// Given the number of points in the curve, map the index of a point to the correct manipulator key
const MANIPULATOR_KEYS_FROM_BEZIER_TYPE: { [key in BezierCurveType]: WasmBezierManipulatorKey[] } = {
[BezierCurveType.Linear]: ["set_start", "set_end"],
[BezierCurveType.Quadratic]: ["set_start", "set_handle_start", "set_end"],
[BezierCurveType.Cubic]: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
};
export default defineComponent({
props: {
title: String,
points: {
type: Array as PropType<Array<Array<number>>>,
required: true,
mutable: true,
},
callback: {
type: Function as PropType<BezierCallback>,
required: true,
},
sliderOptions: {
type: Object as PropType<Array<SliderOption>>,
default: () => ({}),
},
},
data() {
const curveType = getCurveType(this.points.length);
const manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
const bezier = WasmBezier[getConstructorKey(curveType)](this.points);
const sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
return {
bezier,
bezierSVG: this.callback(bezier, sliderData),
manipulatorKeys,
activeIndex: undefined as number | undefined,
mutablePoints: JSON.parse(JSON.stringify(this.points)),
sliderData,
sliderUnits: Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))),
};
},
methods: {
onMouseDown(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
const point = this.mutablePoints[pointIndex];
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
this.activeIndex = pointIndex;
return;
}
}
},
onMouseUp() {
this.activeIndex = undefined;
},
onMouseMove(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
if (this.activeIndex !== undefined) {
this.bezier[this.manipulatorKeys[this.activeIndex]](mx, my);
this.mutablePoints[this.activeIndex] = [mx, my];
this.bezierSVG = this.callback(this.bezier, this.sliderData);
}
},
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
},
watch: {
sliderData: {
handler() {
this.bezierSVG = this.callback(this.bezier, this.sliderData);
},
deep: true,
},
},
});
</script>
<style scoped>
.example-figure {
border: solid 1px black;
width: 200px;
height: 200px;
}
</style>
@@ -0,0 +1,85 @@
<template>
<div>
<h3 class="example-pane-header">{{ name }}</h3>
<div class="example-row">
<div v-for="(example, index) in examples" :key="index">
<BezierExample v-if="!example.disabled" :title="example.title" :points="example.points" :callback="callback" :sliderOptions="example.sliderOptions" />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { BezierCallback, BezierCurveType, ExampleOptions } from "@/utils/types";
import BezierExample from "@/components/BezierExample.vue";
export default defineComponent({
props: {
name: String,
callback: {
type: Function as PropType<BezierCallback>,
required: true,
},
exampleOptions: {
type: Object as PropType<ExampleOptions>,
default: () => ({}),
},
},
data() {
const exampleDefaults = {
[BezierCurveType.Linear]: {
points: [
[30, 60],
[140, 120],
],
},
[BezierCurveType.Quadratic]: {
points: [
[30, 50],
[140, 30],
[160, 170],
],
},
[BezierCurveType.Cubic]: {
points: [
[30, 30],
[60, 140],
[150, 30],
[160, 160],
],
},
};
return {
examples: Object.values(BezierCurveType).map((curveType) => {
const givenData = this.exampleOptions[curveType];
const defaultData = exampleDefaults[curveType];
return {
title: curveType,
disabled: givenData?.disabled || false,
points: givenData?.customPoints || defaultData.points,
sliderOptions: givenData?.sliderOptions || [],
};
}),
};
},
components: {
BezierExample,
},
});
</script>
<style scoped>
.example-row {
display: flex;
flex-direction: row;
justify-content: center;
}
.example-pane-header {
margin-bottom: 0;
}
</style>
@@ -9,7 +9,7 @@
import { defineComponent, PropType } from "vue";
import BezierDrawing from "@/components/BezierDrawing";
import { BezierCallback, WasmBezierInstance } from "@/utils/types";
import { Callback, WasmBezierInstance } from "@/utils/types";
export default defineComponent({
props: {
@@ -19,7 +19,7 @@ export default defineComponent({
required: true,
},
callback: {
type: Function as PropType<BezierCallback>,
type: Function as PropType<Callback>,
required: true,
},
options: {