Bezier-rs: Remove unused legacy drawing components and structs (#853)
* Removed unused legacy drawing components * Removed Point abstraction from frontend code * Code review fixes * Helper lambdas to functions Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
2994afa6b8
commit
a220bfa759
|
|
@ -6,17 +6,6 @@
|
||||||
<div v-for="(feature, index) in bezierFeatures" :key="index">
|
<div v-for="(feature, index) in bezierFeatures" :key="index">
|
||||||
<BezierExamplePane :name="feature.name" :callback="feature.callback" :exampleOptions="feature.exampleOptions" :triggerOnMouseMove="feature.triggerOnMouseMove" />
|
<BezierExamplePane :name="feature.name" :callback="feature.callback" :exampleOptions="feature.exampleOptions" :triggerOnMouseMove="feature.triggerOnMouseMove" />
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO: Remove the below and all associated canvas-related code, then rename `bezierFeatures` to `features` -->
|
|
||||||
<div v-for="(feature, index) in ([] as any)" :key="index">
|
|
||||||
<ExamplePane
|
|
||||||
:template="feature.template"
|
|
||||||
:templateOptions="feature.templateOptions"
|
|
||||||
:name="feature.name"
|
|
||||||
:callback="feature.callback"
|
|
||||||
:curveDegrees="feature.curveDegrees"
|
|
||||||
:customPoints="feature.customPoints"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h2>Subpaths</h2>
|
<h2>Subpaths</h2>
|
||||||
<div v-for="(feature, index) in subpathFeatures" :key="index">
|
<div v-for="(feature, index) in subpathFeatures" :key="index">
|
||||||
<SubpathExamplePane :name="feature.name" :callback="feature.callback" />
|
<SubpathExamplePane :name="feature.name" :callback="feature.callback" />
|
||||||
|
|
@ -28,10 +17,9 @@
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { WasmBezier } from "@/../wasm/pkg";
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
import { BezierCurveType, ExampleOptions, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
import { BezierCurveType, ExampleOptions, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
||||||
|
|
||||||
import BezierExamplePane from "@/components/BezierExamplePane.vue";
|
import BezierExamplePane from "@/components/BezierExamplePane.vue";
|
||||||
import ExamplePane from "@/components/ExamplePane.vue";
|
|
||||||
import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
|
import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
|
||||||
|
|
||||||
const tSliderOptions = {
|
const tSliderOptions = {
|
||||||
|
|
@ -61,12 +49,11 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: "Bezier Through Points",
|
name: "Bezier Through Points",
|
||||||
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
||||||
const points: Point[] = JSON.parse(bezier.get_points());
|
const points = JSON.parse(bezier.get_points());
|
||||||
const formattedPoints: number[][] = points.map((p) => [p.x, p.y]);
|
|
||||||
if (Object.values(options).length === 1) {
|
if (Object.values(options).length === 1) {
|
||||||
return WasmBezier.quadratic_through_points(formattedPoints, options.t);
|
return WasmBezier.quadratic_through_points(points, options.t);
|
||||||
}
|
}
|
||||||
return WasmBezier.cubic_through_points(formattedPoints, options.t, options["midpoint separation"]);
|
return WasmBezier.cubic_through_points(points, options.t, options["midpoint separation"]);
|
||||||
},
|
},
|
||||||
exampleOptions: {
|
exampleOptions: {
|
||||||
[BezierCurveType.Linear]: {
|
[BezierCurveType.Linear]: {
|
||||||
|
|
@ -233,8 +220,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Project",
|
name: "Project",
|
||||||
callback: (bezier: WasmBezierInstance, _: Record<string, number>, mouseLocation: Point): string =>
|
callback: (bezier: WasmBezierInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
|
||||||
mouseLocation ? bezier.project(mouseLocation.x, mouseLocation.y) : bezier.to_svg(),
|
mouseLocation ? bezier.project(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(),
|
||||||
triggerOnMouseMove: true,
|
triggerOnMouseMove: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -529,7 +516,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BezierExamplePane,
|
BezierExamplePane,
|
||||||
ExamplePane,
|
|
||||||
SubpathExamplePane,
|
SubpathExamplePane,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
import { COLORS, drawBezier, drawPoint, getContextFromCanvas, getPointSizeByIndex } from "@/utils/drawing";
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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: { [k: number]: WasmBezierManipulatorKey[] } = {
|
|
||||||
2: ["set_start", "set_end"],
|
|
||||||
3: ["set_start", "set_handle_start", "set_end"],
|
|
||||||
4: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
|
|
||||||
};
|
|
||||||
|
|
||||||
class BezierDrawing {
|
|
||||||
points: BezierPoint[];
|
|
||||||
|
|
||||||
canvas: HTMLCanvasElement;
|
|
||||||
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
dragIndex: number | null;
|
|
||||||
|
|
||||||
bezier: WasmBezierInstance;
|
|
||||||
|
|
||||||
callback: Callback;
|
|
||||||
|
|
||||||
options: Record<string, number>;
|
|
||||||
|
|
||||||
createThroughPoints: boolean;
|
|
||||||
|
|
||||||
constructor(bezier: WasmBezierInstance, callback: Callback, options: Record<string, number>, createThroughPoints = false) {
|
|
||||||
this.bezier = bezier;
|
|
||||||
this.callback = callback;
|
|
||||||
this.options = options;
|
|
||||||
this.createThroughPoints = createThroughPoints;
|
|
||||||
const bezierPoints: Point[] = JSON.parse(bezier.get_points());
|
|
||||||
this.points = bezierPoints.map((p, i, points) => ({
|
|
||||||
x: p.x,
|
|
||||||
y: p.y,
|
|
||||||
r: getPointSizeByIndex(i, points.length),
|
|
||||||
selected: false,
|
|
||||||
manipulator: MANIPULATOR_KEYS_FROM_BEZIER_TYPE[points.length][i],
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (this.createThroughPoints && this.points.length === 4) {
|
|
||||||
// Use the first handler as the middle point
|
|
||||||
this.points = [this.points[0], this.points[1], this.points[3]];
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
if (canvas === null) {
|
|
||||||
throw Error("Failed to create canvas");
|
|
||||||
}
|
|
||||||
this.canvas = canvas;
|
|
||||||
|
|
||||||
this.canvas.style.border = "solid 1px black";
|
|
||||||
this.canvas.width = 200;
|
|
||||||
this.canvas.height = 200;
|
|
||||||
|
|
||||||
this.ctx = getContextFromCanvas(this.canvas);
|
|
||||||
|
|
||||||
this.dragIndex = null; // Index of the point being moved
|
|
||||||
|
|
||||||
this.canvas.addEventListener("mousedown", (e) => this.mouseDownHandler(e));
|
|
||||||
this.canvas.addEventListener("mousemove", (e) => this.mouseMoveHandler(e));
|
|
||||||
this.canvas.addEventListener("mouseup", () => this.deselectPointHandler());
|
|
||||||
this.updateBezier();
|
|
||||||
}
|
|
||||||
|
|
||||||
clearFigure(): void {
|
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseMoveHandler(evt: MouseEvent): void {
|
|
||||||
if (evt.buttons === 0) this.deselectPointHandler();
|
|
||||||
|
|
||||||
const mx = evt.offsetX;
|
|
||||||
const my = evt.offsetY;
|
|
||||||
|
|
||||||
if (this.dragIndex !== null) {
|
|
||||||
const selectableRange = getPointSizeByIndex(this.dragIndex, this.points.length);
|
|
||||||
if (mx - selectableRange > 0 && my - selectableRange > 0 && mx + selectableRange < this.canvas.width && my + selectableRange < this.canvas.height) {
|
|
||||||
const selectedPoint = this.points[this.dragIndex];
|
|
||||||
selectedPoint.x = mx;
|
|
||||||
selectedPoint.y = my;
|
|
||||||
this.bezier[selectedPoint.manipulator](selectedPoint.x, selectedPoint.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateBezier({ x: mx, y: my });
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseDownHandler(evt: MouseEvent): void {
|
|
||||||
const mx = evt.offsetX;
|
|
||||||
const my = evt.offsetY;
|
|
||||||
for (let i = 0; i < this.points.length; i += 1) {
|
|
||||||
const selectableRange = getPointSizeByIndex(i, this.points.length) + FUDGE_FACTOR;
|
|
||||||
if (Math.abs(mx - this.points[i].x) < selectableRange && Math.abs(my - this.points[i].y) < selectableRange) {
|
|
||||||
this.dragIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateBezier();
|
|
||||||
}
|
|
||||||
|
|
||||||
deselectPointHandler(): void {
|
|
||||||
if (this.dragIndex !== undefined) {
|
|
||||||
this.dragIndex = null;
|
|
||||||
this.updateBezier();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBezier(mouseLocation?: Point, options: Record<string, number> = {}): void {
|
|
||||||
this.clearFigure();
|
|
||||||
if (Object.values(options).length !== 0) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
this.clearFigure();
|
|
||||||
|
|
||||||
// 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 pointsToDraw = this.points;
|
|
||||||
|
|
||||||
let styleConfig: Partial<BezierStyleConfig> = {
|
|
||||||
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
|
|
||||||
};
|
|
||||||
let dragIndex = this.dragIndex;
|
|
||||||
if (this.createThroughPoints) {
|
|
||||||
if (this.dragIndex === 1) {
|
|
||||||
// Do not propagate dragIndex when the the non-endpoint is moved
|
|
||||||
dragIndex = null;
|
|
||||||
} else if (this.dragIndex === 2 && pointsToDraw.length === 4) {
|
|
||||||
// For the cubic case, we want to propagate the drag index when the end point is moved, but need to adjust the index
|
|
||||||
dragIndex = 3;
|
|
||||||
}
|
|
||||||
styleConfig = { handleLineStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, handleStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1 };
|
|
||||||
}
|
|
||||||
drawBezier(this.ctx, pointsToDraw, dragIndex, styleConfig);
|
|
||||||
if (this.createThroughPoints) {
|
|
||||||
// Draw the point that the curve was drawn through
|
|
||||||
drawPoint(this.ctx, this.points[1], getPointSizeByIndex(1, this.points.length), this.dragIndex === 1 ? COLORS.INTERACTIVE.SELECTED : COLORS.INTERACTIVE.STROKE_1);
|
|
||||||
}
|
|
||||||
this.callback(this.canvas, this.bezier, this.options, mouseLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCanvas(): HTMLCanvasElement {
|
|
||||||
return this.canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BezierDrawing;
|
|
||||||
|
|
@ -86,7 +86,7 @@ export default defineComponent({
|
||||||
this.mutablePoints[this.activeIndex] = [mx, my];
|
this.mutablePoints[this.activeIndex] = [mx, my];
|
||||||
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
||||||
} else if (this.triggerOnMouseMove) {
|
} else if (this.triggerOnMouseMove) {
|
||||||
this.bezierSVG = this.callback(this.bezier, this.sliderData, { x: mx, y: my });
|
this.bezierSVG = this.callback(this.bezier, this.sliderData, [mx, my]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
|
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h4 class="example-header">{{ title }}</h4>
|
|
||||||
<figure class="example-figure" ref="drawing"></figure>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from "vue";
|
|
||||||
|
|
||||||
import BezierDrawing from "@/components/BezierDrawing";
|
|
||||||
import { Callback, WasmBezierInstance } from "@/utils/types";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
title: String,
|
|
||||||
bezier: {
|
|
||||||
type: Object as PropType<WasmBezierInstance>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
callback: {
|
|
||||||
type: Function as PropType<Callback>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object as PropType<Record<string, number>>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
createThroughPoints: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
bezierDrawing: new BezierDrawing(this.bezier, this.callback, this.options, this.createThroughPoints),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const drawing = this.$refs.drawing as HTMLElement;
|
|
||||||
drawing.appendChild(this.bezierDrawing.getCanvas());
|
|
||||||
this.bezierDrawing.updateBezier();
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
options: {
|
|
||||||
deep: true,
|
|
||||||
handler() {
|
|
||||||
this.bezierDrawing.updateBezier(undefined, this.options);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.example-header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-figure {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h3 class="example-pane-header">{{ name }}</h3>
|
|
||||||
<div class="example-row">
|
|
||||||
<div v-for="(example, index) in exampleData" :key="index">
|
|
||||||
<component :is="template" :templateOptions="example.templateOptions" :title="example.title" :bezier="example.bezier" :callback="callback" :createThroughPoints="createThroughPoints" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType, Component } from "vue";
|
|
||||||
|
|
||||||
import { BezierCallback, BezierCurveType, TemplateOption, WasmBezierConstructorKey, WasmBezierInstance, WasmRawInstance } from "@/utils/types";
|
|
||||||
|
|
||||||
import Example from "@/components/Example.vue";
|
|
||||||
|
|
||||||
type ExampleData = {
|
|
||||||
title: string;
|
|
||||||
bezier: WasmBezierInstance;
|
|
||||||
templateOptions: TemplateOption;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CustomTemplateOptions = {
|
|
||||||
[key in BezierCurveType]?: TemplateOption;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CustomPoints = {
|
|
||||||
[key in BezierCurveType]?: number[][];
|
|
||||||
};
|
|
||||||
|
|
||||||
const CurveTypeMapping = {
|
|
||||||
[BezierCurveType.Linear]: {
|
|
||||||
points: [
|
|
||||||
[30, 60],
|
|
||||||
[140, 120],
|
|
||||||
],
|
|
||||||
constructor: "new_linear" as WasmBezierConstructorKey,
|
|
||||||
},
|
|
||||||
[BezierCurveType.Quadratic]: {
|
|
||||||
points: [
|
|
||||||
[30, 50],
|
|
||||||
[140, 30],
|
|
||||||
[160, 170],
|
|
||||||
],
|
|
||||||
constructor: "new_quadratic" as WasmBezierConstructorKey,
|
|
||||||
},
|
|
||||||
[BezierCurveType.Cubic]: {
|
|
||||||
points: [
|
|
||||||
[30, 30],
|
|
||||||
[60, 140],
|
|
||||||
[150, 30],
|
|
||||||
[160, 160],
|
|
||||||
],
|
|
||||||
constructor: "new_cubic" as WasmBezierConstructorKey,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
name: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
callback: {
|
|
||||||
type: Function as PropType<BezierCallback>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
template: {
|
|
||||||
type: Object as PropType<Component>,
|
|
||||||
default: Example,
|
|
||||||
},
|
|
||||||
templateOptions: {
|
|
||||||
type: Object as PropType<TemplateOption>,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
customOptions: {
|
|
||||||
type: Object as PropType<CustomTemplateOptions>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
createThroughPoints: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
curveDegrees: {
|
|
||||||
type: Set as PropType<Set<BezierCurveType>>,
|
|
||||||
default: () => new Set(Object.values(BezierCurveType)),
|
|
||||||
},
|
|
||||||
customPoints: {
|
|
||||||
type: Object as PropType<CustomPoints>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
exampleData: [] as ExampleData[],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
import("@/../wasm/pkg").then((wasm: WasmRawInstance) => {
|
|
||||||
this.exampleData = [];
|
|
||||||
// Only add example for BezierCurveType that is in the curveDegrees set
|
|
||||||
Object.values(BezierCurveType).forEach((bezierType) => {
|
|
||||||
if (this.curveDegrees.has(bezierType)) {
|
|
||||||
const { points, constructor } = CurveTypeMapping[bezierType];
|
|
||||||
this.exampleData.push({
|
|
||||||
title: bezierType,
|
|
||||||
// Use custom options if they were provided for the current BezierCurveType
|
|
||||||
bezier: wasm.WasmBezier[constructor](this.customPoints[bezierType] || points),
|
|
||||||
templateOptions: (this.customOptions[bezierType] || this.templateOptions) as TemplateOption,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Example,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.example-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-pane-header {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<Example :title="title" :bezier="bezier" :callback="callback" :options="sliderData" :createThroughPoints="createThroughPoints" />
|
|
||||||
<div v-for="(slider, index) in templateOptions.sliders" :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 { BezierCallback, TemplateOption, WasmBezierInstance } from "@/utils/types";
|
|
||||||
|
|
||||||
import Example from "@/components/Example.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
title: String,
|
|
||||||
bezier: {
|
|
||||||
type: Object as PropType<WasmBezierInstance>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
callback: {
|
|
||||||
type: Function as PropType<BezierCallback>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
templateOptions: {
|
|
||||||
type: Object as PropType<TemplateOption>,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
createThroughPoints: {
|
|
||||||
type: Boolean as PropType<boolean>,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const sliders = this.templateOptions.sliders;
|
|
||||||
return {
|
|
||||||
sliderData: Object.assign({}, ...sliders.map((s) => ({ [s.variable]: s.default }))),
|
|
||||||
sliderUnits: Object.assign({}, ...sliders.map((s) => ({ [s.variable]: s.unit }))),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Example,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
import { BezierStyleConfig, CircleSector, Point } from "@/utils/types";
|
|
||||||
|
|
||||||
const HANDLE_RADIUS_FACTOR = 2 / 3;
|
|
||||||
const DEFAULT_ENDPOINT_RADIUS = 5;
|
|
||||||
|
|
||||||
export const COLORS = {
|
|
||||||
CANVAS: "white",
|
|
||||||
INTERACTIVE: {
|
|
||||||
STROKE_1: "black",
|
|
||||||
STROKE_2: "gray",
|
|
||||||
SELECTED: "blue",
|
|
||||||
},
|
|
||||||
NON_INTERACTIVE: {
|
|
||||||
STROKE_1: "red",
|
|
||||||
STROKE_2: "orange",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isIndexFirstOrLast = (index: number, arrayLength: number): boolean => index === 0 || index === arrayLength - 1;
|
|
||||||
|
|
||||||
export const getPointSizeByIndex = (index: number, numPoints: number, radius = DEFAULT_ENDPOINT_RADIUS): number => (isIndexFirstOrLast(index, numPoints) ? radius : radius * HANDLE_RADIUS_FACTOR);
|
|
||||||
|
|
||||||
export const getContextFromCanvas = (canvas: HTMLCanvasElement): CanvasRenderingContext2D => {
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (ctx === null) {
|
|
||||||
throw Error("Failed to fetch context");
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawLine = (ctx: CanvasRenderingContext2D, point1: Point, point2: Point, strokeColor = COLORS.INTERACTIVE.STROKE_2, lineWidth = 1): void => {
|
|
||||||
ctx.strokeStyle = strokeColor;
|
|
||||||
ctx.lineWidth = lineWidth;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(point1.x, point1.y);
|
|
||||||
ctx.lineTo(point2.x, point2.y);
|
|
||||||
ctx.stroke();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawPoint = (ctx: CanvasRenderingContext2D, point: Point, radius: number, strokeColor = COLORS.INTERACTIVE.STROKE_1): void => {
|
|
||||||
// Outline the point
|
|
||||||
ctx.strokeStyle = strokeColor;
|
|
||||||
ctx.lineWidth = radius / 3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI, false);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Fill the point (hiding any overlapping lines)
|
|
||||||
ctx.fillStyle = COLORS.CANVAS;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(point.x, point.y, radius * HANDLE_RADIUS_FACTOR, 0, 2 * Math.PI, false);
|
|
||||||
ctx.fill();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawText = (ctx: CanvasRenderingContext2D, text: string, x: number, y: number, textColor = COLORS.INTERACTIVE.STROKE_1): void => {
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.font = "16px Arial";
|
|
||||||
ctx.fillText(text, x, y);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawCurve = (ctx: CanvasRenderingContext2D, points: Point[], strokeColor = COLORS.INTERACTIVE.STROKE_1, lineWidth = 2): void => {
|
|
||||||
ctx.strokeStyle = strokeColor;
|
|
||||||
ctx.lineWidth = lineWidth;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(points[0].x, points[0].y);
|
|
||||||
if (points.length === 3) {
|
|
||||||
ctx.quadraticCurveTo(points[1].x, points[1].y, points[2].x, points[2].y);
|
|
||||||
} else {
|
|
||||||
ctx.bezierCurveTo(points[1].x, points[1].y, points[2].x, points[2].y, points[3].x, points[3].y);
|
|
||||||
}
|
|
||||||
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 drawCircleSector = (ctx: CanvasRenderingContext2D, circleSector: CircleSector, strokeColor = COLORS.INTERACTIVE.STROKE_1, fillColor = COLORS.NON_INTERACTIVE.STROKE_1): void => {
|
|
||||||
ctx.strokeStyle = strokeColor;
|
|
||||||
ctx.fillStyle = fillColor;
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
|
|
||||||
const { center, radius, startAngle, endAngle } = circleSector;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(center.x, center.y);
|
|
||||||
ctx.arc(center.x, center.y, radius, startAngle, endAngle);
|
|
||||||
ctx.lineTo(center.x, center.y);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawBezier = (ctx: CanvasRenderingContext2D, points: Point[], dragIndex: number | null = null, bezierStyleConfig: Partial<BezierStyleConfig> = {}): void => {
|
|
||||||
const styleConfig: BezierStyleConfig = {
|
|
||||||
curveStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
||||||
handleStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
||||||
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_1,
|
|
||||||
radius: DEFAULT_ENDPOINT_RADIUS,
|
|
||||||
drawHandles: true,
|
|
||||||
...bezierStyleConfig,
|
|
||||||
};
|
|
||||||
// If the handle or handle line colors are not specified, use the same color as the rest of the curve
|
|
||||||
if (bezierStyleConfig.curveStrokeColor) {
|
|
||||||
if (!bezierStyleConfig.handleStrokeColor) {
|
|
||||||
styleConfig.handleStrokeColor = bezierStyleConfig.curveStrokeColor;
|
|
||||||
}
|
|
||||||
if (!bezierStyleConfig.handleLineStrokeColor) {
|
|
||||||
styleConfig.handleLineStrokeColor = bezierStyleConfig.curveStrokeColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Points passed to drawBezier are interpreted as follows
|
|
||||||
// points[0] = start point
|
|
||||||
// points[1] = handle start
|
|
||||||
// points[2] = (optional) handle end
|
|
||||||
// points[3] = end point
|
|
||||||
const start = points[0];
|
|
||||||
let end = null;
|
|
||||||
let handleStart = null;
|
|
||||||
let handleEnd = null;
|
|
||||||
if (points.length === 4) {
|
|
||||||
handleStart = points[1];
|
|
||||||
handleEnd = points[2];
|
|
||||||
end = points[3];
|
|
||||||
} else if (points.length === 3) {
|
|
||||||
handleStart = points[1];
|
|
||||||
handleEnd = handleStart;
|
|
||||||
end = points[2];
|
|
||||||
} else {
|
|
||||||
handleStart = start;
|
|
||||||
handleEnd = points[1];
|
|
||||||
end = handleEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (points.length === 2) {
|
|
||||||
drawLine(ctx, start, end, styleConfig.curveStrokeColor, 2);
|
|
||||||
} else {
|
|
||||||
drawCurve(ctx, points, styleConfig.curveStrokeColor);
|
|
||||||
if (styleConfig.drawHandles) {
|
|
||||||
drawLine(ctx, start, handleStart, styleConfig.handleLineStrokeColor);
|
|
||||||
drawLine(ctx, end, handleEnd, styleConfig.handleLineStrokeColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
points.forEach((point, index) => {
|
|
||||||
if (styleConfig.drawHandles || isIndexFirstOrLast(index, points.length)) {
|
|
||||||
const strokeColor = isIndexFirstOrLast(index, points.length) ? styleConfig.curveStrokeColor : styleConfig.handleStrokeColor;
|
|
||||||
drawPoint(ctx, point, getPointSizeByIndex(index, points.length, styleConfig.radius), index === dragIndex ? COLORS.INTERACTIVE.SELECTED : strokeColor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { BezierCurveType, WasmBezierConstructorKey } from "@/utils/types";
|
import { BezierCurveType, WasmBezierConstructorKey } from "@/utils/types";
|
||||||
|
|
||||||
export const getCurveType = (numPoints: number): BezierCurveType => {
|
export function getCurveType(numPoints: number): BezierCurveType {
|
||||||
switch (numPoints) {
|
switch (numPoints) {
|
||||||
case 2:
|
case 2:
|
||||||
return BezierCurveType.Linear;
|
return BezierCurveType.Linear;
|
||||||
|
|
@ -11,9 +11,9 @@ export const getCurveType = (numPoints: number): BezierCurveType => {
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid number of points for a bezier");
|
throw new Error("Invalid number of points for a bezier");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getConstructorKey = (bezierCurveType: BezierCurveType): WasmBezierConstructorKey => {
|
export function getConstructorKey(bezierCurveType: BezierCurveType): WasmBezierConstructorKey {
|
||||||
switch (bezierCurveType) {
|
switch (bezierCurveType) {
|
||||||
case BezierCurveType.Linear:
|
case BezierCurveType.Linear:
|
||||||
return "new_linear";
|
return "new_linear";
|
||||||
|
|
@ -24,4 +24,4 @@ export const getConstructorKey = (bezierCurveType: BezierCurveType): WasmBezierC
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid value for a BezierCurveType");
|
throw new Error("Invalid value for a BezierCurveType");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@ export enum BezierCurveType {
|
||||||
Cubic = "Cubic",
|
Cubic = "Cubic",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Callback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: Point) => void;
|
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string;
|
||||||
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: Point) => string;
|
|
||||||
export type SubpathCallback = (subpath: WasmSubpathInstance) => string;
|
export type SubpathCallback = (subpath: WasmSubpathInstance) => string;
|
||||||
|
|
||||||
export type ExampleOptions = {
|
export type ExampleOptions = {
|
||||||
|
|
@ -34,31 +33,3 @@ export type SliderOption = {
|
||||||
variable: string;
|
variable: string;
|
||||||
unit?: string | string[];
|
unit?: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TemplateOption = {
|
|
||||||
sliders: SliderOption[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Point = {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BezierPoint = Point & {
|
|
||||||
manipulator: WasmBezierManipulatorKey;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BezierStyleConfig = {
|
|
||||||
curveStrokeColor: string;
|
|
||||||
handleStrokeColor: string;
|
|
||||||
handleLineStrokeColor: string;
|
|
||||||
radius: number;
|
|
||||||
drawHandles: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CircleSector = {
|
|
||||||
center: Point;
|
|
||||||
radius: number;
|
|
||||||
startAngle: number;
|
|
||||||
endAngle: number;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct CircleSector {
|
struct CircleSector {
|
||||||
center: Point,
|
center: DVec2,
|
||||||
radius: f64,
|
radius: f64,
|
||||||
#[serde(rename = "startAngle")]
|
#[serde(rename = "startAngle")]
|
||||||
start_angle: f64,
|
start_angle: f64,
|
||||||
|
|
@ -14,12 +14,6 @@ struct CircleSector {
|
||||||
end_angle: f64,
|
end_angle: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Point {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub enum WasmMaximizeArcs {
|
pub enum WasmMaximizeArcs {
|
||||||
Automatic, // 0
|
Automatic, // 0
|
||||||
|
|
@ -34,11 +28,6 @@ const SCALE_UNIT_VECTOR_FACTOR: f64 = 50.;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WasmBezier(Bezier);
|
pub struct WasmBezier(Bezier);
|
||||||
|
|
||||||
/// Convert a `DVec2` into a `Point`.
|
|
||||||
fn vec_to_point(p: &DVec2) -> Point {
|
|
||||||
Point { x: p.x, y: p.y }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialize some data and then convert it to a JsValue.
|
/// Serialize some data and then convert it to a JsValue.
|
||||||
fn to_js_value<T: Serialize>(data: T) -> JsValue {
|
fn to_js_value<T: Serialize>(data: T) -> JsValue {
|
||||||
JsValue::from_serde(&serde_json::to_string(&data).unwrap()).unwrap()
|
JsValue::from_serde(&serde_json::to_string(&data).unwrap()).unwrap()
|
||||||
|
|
@ -85,7 +74,7 @@ impl WasmBezier {
|
||||||
HANDLE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
HANDLE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
||||||
HANDLE_LINE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
HANDLE_LINE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
||||||
);
|
);
|
||||||
let through_point_circle = format!(r#"<circle cx="{}" cy="{}" {}/>"#, through_point.x, through_point.y, ANCHOR_ATTRIBUTES.to_string());
|
let through_point_circle = format!(r#"<circle cx="{}" cy="{}" {}/>"#, through_point.x, through_point.y, ANCHOR_ATTRIBUTES);
|
||||||
|
|
||||||
wrap_svg_tag(format!("{bezier_string}{through_point_circle}"))
|
wrap_svg_tag(format!("{bezier_string}{through_point_circle}"))
|
||||||
}
|
}
|
||||||
|
|
@ -118,10 +107,8 @@ impl WasmBezier {
|
||||||
self.0.set_handle_end(DVec2::new(x, y));
|
self.0.set_handle_end(DVec2::new(x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wrapped return type is `Vec<Point>`.
|
|
||||||
pub fn get_points(&self) -> JsValue {
|
pub fn get_points(&self) -> JsValue {
|
||||||
let points: Vec<Point> = self.0.get_points().map(|point| vec_to_point(&point)).collect();
|
to_js_value(self.0.get_points().collect::<Vec<DVec2>>())
|
||||||
to_js_value(points)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bezier_path(&self) -> String {
|
fn get_bezier_path(&self) -> String {
|
||||||
|
|
@ -145,25 +132,19 @@ impl WasmBezier {
|
||||||
wrap_svg_tag(format!("{bezier}{}", draw_text(format!("Length: {:.2}", self.0.length(None)), TEXT_OFFSET_X, TEXT_OFFSET_Y, BLACK)))
|
wrap_svg_tag(format!("{bezier}{}", draw_text(format!("Length: {:.2}", self.0.length(None)), TEXT_OFFSET_X, TEXT_OFFSET_Y, BLACK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wrapped return type is `Point`.
|
|
||||||
pub fn evaluate_value(&self, t: f64) -> JsValue {
|
|
||||||
let point: Point = vec_to_point(&self.0.evaluate(t));
|
|
||||||
to_js_value(point)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate(&self, t: f64) -> String {
|
pub fn evaluate(&self, t: f64) -> String {
|
||||||
let bezier = self.get_bezier_path();
|
let bezier = self.get_bezier_path();
|
||||||
let point = &self.0.evaluate(t);
|
let point = &self.0.evaluate(t);
|
||||||
let content = format!("{bezier}{}", draw_circle(point.x, point.y, 4., RED, 1.5, WHITE));
|
let content = format!("{bezier}{}", draw_circle(*point, 4., RED, 1.5, WHITE));
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_lookup_table(&self, steps: usize) -> String {
|
pub fn compute_lookup_table(&self, steps: usize) -> String {
|
||||||
let bezier = self.get_bezier_path();
|
let bezier = self.get_bezier_path();
|
||||||
let table_values: Vec<Point> = self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect();
|
let table_values: Vec<DVec2> = self.0.compute_lookup_table(Some(steps));
|
||||||
let circles: String = table_values
|
let circles: String = table_values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|point| draw_circle(point.x, point.y, 3., RED, 1.5, WHITE))
|
.map(|point| draw_circle(*point, 3., RED, 1.5, WHITE))
|
||||||
.fold("".to_string(), |acc, circle| acc + &circle);
|
.fold("".to_string(), |acc, circle| acc + &circle);
|
||||||
let content = format!("{bezier}{circles}");
|
let content = format!("{bezier}{circles}");
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
|
|
@ -188,7 +169,6 @@ impl WasmBezier {
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wrapped return type is `Point`.
|
|
||||||
pub fn tangent(&self, t: f64) -> String {
|
pub fn tangent(&self, t: f64) -> String {
|
||||||
let bezier = self.get_bezier_path();
|
let bezier = self.get_bezier_path();
|
||||||
|
|
||||||
|
|
@ -198,9 +178,9 @@ impl WasmBezier {
|
||||||
|
|
||||||
let content = format!(
|
let content = format!(
|
||||||
"{bezier}{}{}{}",
|
"{bezier}{}{}{}",
|
||||||
draw_circle(intersection_point.x, intersection_point.y, 3., RED, 1., WHITE),
|
draw_circle(intersection_point, 3., RED, 1., WHITE),
|
||||||
draw_line(intersection_point.x, intersection_point.y, tangent_end.x, tangent_end.y, RED, 1.),
|
draw_line(intersection_point.x, intersection_point.y, tangent_end.x, tangent_end.y, RED, 1.),
|
||||||
draw_circle(tangent_end.x, tangent_end.y, 3., RED, 1., WHITE),
|
draw_circle(tangent_end, 3., RED, 1., WHITE),
|
||||||
);
|
);
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
@ -215,8 +195,8 @@ impl WasmBezier {
|
||||||
let content = format!(
|
let content = format!(
|
||||||
"{bezier}{}{}{}",
|
"{bezier}{}{}{}",
|
||||||
draw_line(intersection_point.x, intersection_point.y, normal_end.x, normal_end.y, RED, 1.),
|
draw_line(intersection_point.x, intersection_point.y, normal_end.x, normal_end.y, RED, 1.),
|
||||||
draw_circle(intersection_point.x, intersection_point.y, 3., RED, 1., WHITE),
|
draw_circle(intersection_point, 3., RED, 1., WHITE),
|
||||||
draw_circle(normal_end.x, normal_end.y, 3., RED, 1., WHITE),
|
draw_circle(normal_end, 3., RED, 1., WHITE),
|
||||||
);
|
);
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
@ -231,10 +211,10 @@ impl WasmBezier {
|
||||||
|
|
||||||
let content = format!(
|
let content = format!(
|
||||||
"{bezier}{}{}{}{}",
|
"{bezier}{}{}{}{}",
|
||||||
draw_circle(curvature_center.x, curvature_center.y, radius.abs(), RED, 1., NONE),
|
draw_circle(curvature_center, radius.abs(), RED, 1., NONE),
|
||||||
draw_line(intersection_point.x, intersection_point.y, curvature_center.x, curvature_center.y, RED, 1.),
|
draw_line(intersection_point.x, intersection_point.y, curvature_center.x, curvature_center.y, RED, 1.),
|
||||||
draw_circle(intersection_point.x, intersection_point.y, 3., RED, 1., WHITE),
|
draw_circle(intersection_point, 3., RED, 1., WHITE),
|
||||||
draw_circle(curvature_center.x, curvature_center.y, 3., RED, 1., WHITE),
|
draw_circle(curvature_center, 3., RED, 1., WHITE),
|
||||||
);
|
);
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +286,7 @@ impl WasmBezier {
|
||||||
.flat_map(|(t_value_list, color)| {
|
.flat_map(|(t_value_list, color)| {
|
||||||
t_value_list.iter().map(|&t_value| {
|
t_value_list.iter().map(|&t_value| {
|
||||||
let point = self.0.evaluate(t_value);
|
let point = self.0.evaluate(t_value);
|
||||||
draw_circle(point.x, point.y, 3., color, 1.5, WHITE)
|
draw_circle(point, 3., color, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.fold("".to_string(), |acc, circle| acc + &circle);
|
.fold("".to_string(), |acc, circle| acc + &circle);
|
||||||
|
|
@ -341,7 +321,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&t_value| {
|
.map(|&t_value| {
|
||||||
let point = self.0.evaluate(t_value);
|
let point = self.0.evaluate(t_value);
|
||||||
draw_circle(point.x, point.y, 3., RED, 1.5, WHITE)
|
draw_circle(point, 3., RED, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
.fold("".to_string(), |acc, circle| acc + &circle);
|
.fold("".to_string(), |acc, circle| acc + &circle);
|
||||||
let content = format!("{bezier}{circles}");
|
let content = format!("{bezier}{circles}");
|
||||||
|
|
@ -362,7 +342,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, point)| {
|
.map(|(index, point)| {
|
||||||
let circle = draw_circle(point.x, point.y, 3., &color_light, 1.5, WHITE);
|
let circle = draw_circle(*point, 3., &color_light, 1.5, WHITE);
|
||||||
if index != 0 {
|
if index != 0 {
|
||||||
let prev_point = points[index - 1];
|
let prev_point = points[index - 1];
|
||||||
let line = draw_line(prev_point.x, prev_point.y, point.x, point.y, &color_light, 1.5);
|
let line = draw_line(prev_point.x, prev_point.y, point.x, point.y, &color_light, 1.5);
|
||||||
|
|
@ -385,7 +365,7 @@ impl WasmBezier {
|
||||||
let rotated_bezier = self.0.rotate_about_point(angle, DVec2::new(pivot_x, pivot_y));
|
let rotated_bezier = self.0.rotate_about_point(angle, DVec2::new(pivot_x, pivot_y));
|
||||||
let mut rotated_bezier_svg = String::new();
|
let mut rotated_bezier_svg = String::new();
|
||||||
rotated_bezier.to_svg(&mut rotated_bezier_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
|
rotated_bezier.to_svg(&mut rotated_bezier_svg, CURVE_ATTRIBUTES.to_string().replace(BLACK, RED), String::new(), String::new(), String::new());
|
||||||
let pivot = draw_circle(pivot_x, pivot_y, 3., GRAY, 1.5, WHITE);
|
let pivot = draw_circle(DVec2::new(pivot_x, pivot_y), 3., GRAY, 1.5, WHITE);
|
||||||
|
|
||||||
// Line between pivot and start point on curve
|
// Line between pivot and start point on curve
|
||||||
let original_dashed_line_start = format!(
|
let original_dashed_line_start = format!(
|
||||||
|
|
@ -434,7 +414,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|intersection_t| {
|
.map(|intersection_t| {
|
||||||
let point = &self.0.evaluate(*intersection_t);
|
let point = &self.0.evaluate(*intersection_t);
|
||||||
draw_circle(point.x, point.y, 4., RED, 1.5, WHITE)
|
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||||
wrap_svg_tag(format!("{bezier_curve_svg}{line_svg}{intersections_svg}"))
|
wrap_svg_tag(format!("{bezier_curve_svg}{line_svg}{intersections_svg}"))
|
||||||
|
|
@ -454,7 +434,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|intersection_t| {
|
.map(|intersection_t| {
|
||||||
let point = &self.0.evaluate(*intersection_t);
|
let point = &self.0.evaluate(*intersection_t);
|
||||||
draw_circle(point.x, point.y, 4., RED, 1.5, WHITE)
|
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||||
wrap_svg_tag(format!("{bezier_curve_svg}{quadratic_svg}{intersections_svg}"))
|
wrap_svg_tag(format!("{bezier_curve_svg}{quadratic_svg}{intersections_svg}"))
|
||||||
|
|
@ -474,7 +454,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|intersection_t| {
|
.map(|intersection_t| {
|
||||||
let point = &self.0.evaluate(*intersection_t);
|
let point = &self.0.evaluate(*intersection_t);
|
||||||
draw_circle(point.x, point.y, 4., RED, 1.5, WHITE)
|
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
.fold(String::new(), |acc, item| format!("{acc}{item}"));
|
||||||
|
|
||||||
|
|
@ -490,7 +470,7 @@ impl WasmBezier {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|intersection_t| {
|
.map(|intersection_t| {
|
||||||
let point = &self.0.evaluate(intersection_t[0]);
|
let point = &self.0.evaluate(intersection_t[0]);
|
||||||
draw_circle(point.x, point.y, 4., RED, 1.5, WHITE)
|
draw_circle(*point, 4., RED, 1.5, WHITE)
|
||||||
})
|
})
|
||||||
.fold(bezier_curve_svg, |acc, item| format!("{acc}{item}"));
|
.fold(bezier_curve_svg, |acc, item| format!("{acc}{item}"));
|
||||||
|
|
||||||
|
|
@ -577,7 +557,6 @@ impl WasmBezier {
|
||||||
wrap_svg_tag(format!("{bezier_svg}{outline_svg}"))
|
wrap_svg_tag(format!("{bezier_svg}{outline_svg}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wrapped return type is `Vec<CircleSector>`.
|
|
||||||
pub fn arcs(&self, error: f64, max_iterations: usize, maximize_arcs: WasmMaximizeArcs) -> String {
|
pub fn arcs(&self, error: f64, max_iterations: usize, maximize_arcs: WasmMaximizeArcs) -> String {
|
||||||
let original_curve_svg = self.get_bezier_path();
|
let original_curve_svg = self.get_bezier_path();
|
||||||
|
|
||||||
|
|
@ -591,8 +570,7 @@ impl WasmBezier {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, sector)| {
|
.map(|(idx, sector)| {
|
||||||
draw_sector(
|
draw_sector(
|
||||||
sector.center.x,
|
sector.center,
|
||||||
sector.center.y,
|
|
||||||
sector.radius,
|
sector.radius,
|
||||||
-sector.start_angle,
|
-sector.start_angle,
|
||||||
-sector.end_angle,
|
-sector.end_angle,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use bezier_rs::Bezier;
|
use bezier_rs::Bezier;
|
||||||
|
use glam::DVec2;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
// SVG drawing constants
|
// SVG drawing constants
|
||||||
|
|
@ -31,8 +32,11 @@ pub fn draw_text(text: String, x_pos: f64, y_pos: f64, fill: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to create an SVG circle entity.
|
/// Helper function to create an SVG circle entity.
|
||||||
pub fn draw_circle(x_pos: f64, y_pos: f64, radius: f64, stroke: &str, stroke_width: f64, fill: &str) -> String {
|
pub fn draw_circle(position: DVec2, radius: f64, stroke: &str, stroke_width: f64, fill: &str) -> String {
|
||||||
format!(r#"<circle cx="{x_pos}" cy="{y_pos}" r="{radius}" stroke="{stroke}" stroke-width="{stroke_width}" fill="{fill}"/>"#)
|
format!(
|
||||||
|
r#"<circle cx="{}" cy="{}" r="{radius}" stroke="{stroke}" stroke-width="{stroke_width}" fill="{fill}"/>"#,
|
||||||
|
position.x, position.y
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to create an SVG circle entity.
|
/// Helper function to create an SVG circle entity.
|
||||||
|
|
@ -61,11 +65,14 @@ fn polar_to_cartesian(center_x: f64, center_y: f64, radius: f64, angle_in_rad: f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create an SVG drawing of a sector
|
// Helper function to create an SVG drawing of a sector
|
||||||
pub fn draw_sector(center_x: f64, center_y: f64, radius: f64, start_angle: f64, end_angle: f64, stroke: &str, stroke_width: f64, fill: &str) -> String {
|
pub fn draw_sector(center: DVec2, radius: f64, start_angle: f64, end_angle: f64, stroke: &str, stroke_width: f64, fill: &str) -> String {
|
||||||
let [start_x, start_y] = polar_to_cartesian(center_x, center_y, radius, start_angle);
|
let [start_x, start_y] = polar_to_cartesian(center.x, center.y, radius, start_angle);
|
||||||
let [end_x, end_y] = polar_to_cartesian(center_x, center_y, radius, end_angle);
|
let [end_x, end_y] = polar_to_cartesian(center.x, center.y, radius, end_angle);
|
||||||
// draw sector with fill color
|
// draw sector with fill color
|
||||||
let sector_svg = format!(r#"<path d="M {start_x} {start_y} A {radius} {radius} 0 0 1 {end_x} {end_y} L {center_x} {center_y} L {start_x} {start_y} Z" stroke="none" fill="{fill}" />"#);
|
let sector_svg = format!(
|
||||||
|
r#"<path d="M {start_x} {start_y} A {radius} {radius} 0 0 1 {end_x} {end_y} L {} {} L {start_x} {start_y} Z" stroke="none" fill="{fill}" />"#,
|
||||||
|
center.x, center.y
|
||||||
|
);
|
||||||
// draw arc with stroke color
|
// draw arc with stroke color
|
||||||
let arc_svg = format!(r#"<path d="M {start_x} {start_y} A {radius} {radius} 0 0 1 {end_x} {end_y}" stroke="{stroke}" stroke-width="{stroke_width}" fill="none"/>"#);
|
let arc_svg = format!(r#"<path d="M {start_x} {start_y} A {radius} {radius} 0 0 1 {end_x} {end_y}" stroke="{stroke}" stroke-width="{stroke_width}" fill="none"/>"#);
|
||||||
format!("{sector_svg}{arc_svg}")
|
format!("{sector_svg}{arc_svg}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue