mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 15:38:12 +08:00
Make certain Bezier function parameters optional and other refactors (#713)
* Make certain parameters optional * Use builder pattern for project function's optional parameters * Address comments posted in bezier-math-lib discord channel * Minor changes to text * Address PR comments * Fix index.html * Nit * Replace builder pattern with simple struct * Move constants to a separate file Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
9ed9db7009
commit
f3d5028107
@@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "vue-cli-service serve",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,17 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Bezier-rs Interactive Docs</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>JavaScript is required</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
:cubicOptions="feature.cubicOptions"
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div id="svg-test" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,21 +24,6 @@ import { Point, WasmBezierInstance } from "@/utils/types";
|
||||
import ExamplePane from "@/components/ExamplePane.vue";
|
||||
import SliderExample from "@/components/SliderExample.vue";
|
||||
|
||||
// eslint-disable-next-line
|
||||
const testBezierLib = async () => {
|
||||
import("@/../wasm/pkg").then((wasm) => {
|
||||
const bezier = wasm.WasmBezier.new_quadratic([
|
||||
[0, 0],
|
||||
[50, 0],
|
||||
[100, 100],
|
||||
]);
|
||||
const svgContainer = document.getElementById("svg-test");
|
||||
if (svgContainer) {
|
||||
svgContainer.innerHTML = bezier.to_svg();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const tSliderOptions = {
|
||||
min: 0,
|
||||
max: 1,
|
||||
@@ -49,6 +32,8 @@ const tSliderOptions = {
|
||||
variable: "t",
|
||||
};
|
||||
|
||||
const SCALE_UNIT_VECTOR_FACTOR = 50;
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
components: {
|
||||
@@ -63,7 +48,7 @@ export default defineComponent({
|
||||
callback: (): void => {},
|
||||
},
|
||||
{
|
||||
name: "Bezier through points",
|
||||
name: "Bezier Through Points",
|
||||
// eslint-disable-next-line
|
||||
callback: (): void => {},
|
||||
createThroughPoints: true,
|
||||
@@ -137,26 +122,20 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Derivative",
|
||||
name: "Tangent",
|
||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||
const context = getContextFromCanvas(canvas);
|
||||
|
||||
const intersection = JSON.parse(bezier.compute(options.t));
|
||||
const derivative = JSON.parse(bezier.derivative(options.t));
|
||||
const curveFactor = bezier.get_points().length - 1;
|
||||
const tangent = JSON.parse(bezier.tangent(options.t));
|
||||
|
||||
const tangentStart = {
|
||||
x: intersection.x - derivative.x / curveFactor,
|
||||
y: intersection.y - derivative.y / curveFactor,
|
||||
};
|
||||
const tangentEnd = {
|
||||
x: intersection.x + derivative.x / curveFactor,
|
||||
y: intersection.y + derivative.y / curveFactor,
|
||||
x: intersection.x + tangent.x * SCALE_UNIT_VECTOR_FACTOR,
|
||||
y: intersection.y + tangent.y * SCALE_UNIT_VECTOR_FACTOR,
|
||||
};
|
||||
|
||||
drawLine(context, tangentStart, tangentEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, tangentStart, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawLine(context, intersection, tangentEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, tangentEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
},
|
||||
template: markRaw(SliderExample),
|
||||
@@ -170,18 +149,13 @@ export default defineComponent({
|
||||
const intersection = JSON.parse(bezier.compute(options.t));
|
||||
const normal = JSON.parse(bezier.normal(options.t));
|
||||
|
||||
const normalStart = {
|
||||
x: intersection.x - normal.x * 20,
|
||||
y: intersection.y - normal.y * 20,
|
||||
};
|
||||
const normalEnd = {
|
||||
x: intersection.x + normal.x * 20,
|
||||
y: intersection.y + normal.y * 20,
|
||||
x: intersection.x - normal.x * SCALE_UNIT_VECTOR_FACTOR,
|
||||
y: intersection.y - normal.y * SCALE_UNIT_VECTOR_FACTOR,
|
||||
};
|
||||
|
||||
drawLine(context, normalStart, normalEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, normalStart, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, intersection, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawLine(context, intersection, normalEnd, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(context, normalEnd, 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
},
|
||||
template: markRaw(SliderExample),
|
||||
@@ -240,14 +214,16 @@ export default defineComponent({
|
||||
name: "Local Extrema",
|
||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||
const context = getContextFromCanvas(canvas);
|
||||
const dimensionColors = [COLORS.NON_INTERACTIVE.STROKE_1, COLORS.NON_INTERACTIVE.STROKE_2];
|
||||
const dimensionColors = ["red", "green"];
|
||||
const extrema: number[][] = JSON.parse(bezier.local_extrema());
|
||||
extrema.forEach((tValues, index) => {
|
||||
tValues.forEach((t) => {
|
||||
const point = JSON.parse(bezier.compute(t));
|
||||
const point: Point = JSON.parse(bezier.compute(t));
|
||||
drawPoint(context, point, 4, dimensionColors[index]);
|
||||
});
|
||||
});
|
||||
drawText(getContextFromCanvas(canvas), "X extrema", 5, canvas.height - 20, dimensionColors[0]);
|
||||
drawText(getContextFromCanvas(canvas), "Y extrema", 5, canvas.height - 5, dimensionColors[1]);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -255,7 +231,7 @@ export default defineComponent({
|
||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||
const context = getContextFromCanvas(canvas);
|
||||
const rotatedBezier = bezier
|
||||
.rotate((options.angle * Math.PI) / 180)
|
||||
.rotate(options.angle * Math.PI)
|
||||
.get_points()
|
||||
.map((p) => JSON.parse(p));
|
||||
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
|
||||
@@ -265,25 +241,26 @@ export default defineComponent({
|
||||
sliders: [
|
||||
{
|
||||
variable: "angle",
|
||||
min: -90,
|
||||
max: 90,
|
||||
step: 5,
|
||||
default: 15,
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 1 / 16,
|
||||
default: 1 / 8,
|
||||
unit: "π",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Line Intersection",
|
||||
name: "Intersect Line Segment",
|
||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||
const context = getContextFromCanvas(canvas);
|
||||
const line = [
|
||||
{ x: 150, y: 150 },
|
||||
{ x: 30, y: 30 },
|
||||
{ x: 20, y: 20 },
|
||||
];
|
||||
const mappedLine = line.map((p) => [p.x, p.y]);
|
||||
drawLine(context, line[0], line[1], COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
const intersections: Point[] = bezier.line_intersection(mappedLine).map((p) => JSON.parse(p));
|
||||
const intersections: Point[] = bezier.intersect_line_segment(mappedLine).map((p) => JSON.parse(p));
|
||||
intersections.forEach((p: Point) => {
|
||||
drawPoint(context, p, 3, COLORS.NON_INTERACTIVE.STROKE_2);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<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] }}</div>
|
||||
<div class="slider_label">{{ slider.variable }} = {{ 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>
|
||||
@@ -43,6 +43,7 @@ export default defineComponent({
|
||||
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 }))),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ export type SliderOption = {
|
||||
step: number;
|
||||
default: number;
|
||||
variable: string;
|
||||
unit?: string;
|
||||
};
|
||||
|
||||
export type TemplateOption = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use bezier_rs::Bezier;
|
||||
use bezier_rs::{Bezier, ProjectionOptions};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -35,12 +35,12 @@ impl WasmBezier {
|
||||
|
||||
pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier {
|
||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||
WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], t))
|
||||
WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t)))
|
||||
}
|
||||
|
||||
pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier {
|
||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||
WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], t, midpoint_separation))
|
||||
WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation)))
|
||||
}
|
||||
|
||||
pub fn set_start(&mut self, x: f64, y: f64) {
|
||||
@@ -79,8 +79,8 @@ impl WasmBezier {
|
||||
self.0.compute_lookup_table(Some(steps)).iter().map(vec_to_point).collect()
|
||||
}
|
||||
|
||||
pub fn derivative(&self, t: f64) -> JsValue {
|
||||
vec_to_point(&self.0.derivative(t))
|
||||
pub fn tangent(&self, t: f64) -> JsValue {
|
||||
vec_to_point(&self.0.tangent(t))
|
||||
}
|
||||
|
||||
pub fn normal(&self, t: f64) -> JsValue {
|
||||
@@ -100,7 +100,7 @@ impl WasmBezier {
|
||||
}
|
||||
|
||||
pub fn project(&self, x: f64, y: f64) -> JsValue {
|
||||
vec_to_point(&self.0.project(DVec2::new(x, y), 20, 1e-4, 3, 10))
|
||||
vec_to_point(&self.0.project(DVec2::new(x, y), ProjectionOptions::default()))
|
||||
}
|
||||
|
||||
pub fn local_extrema(&self) -> JsValue {
|
||||
@@ -112,8 +112,8 @@ impl WasmBezier {
|
||||
WasmBezier(self.0.rotate(angle))
|
||||
}
|
||||
|
||||
pub fn line_intersection(&self, js_points: &JsValue) -> Vec<JsValue> {
|
||||
pub fn intersect_line_segment(&self, js_points: &JsValue) -> Vec<JsValue> {
|
||||
let line: [DVec2; 2] = js_points.into_serde().unwrap();
|
||||
self.0.line_intersection(line).iter().map(|&p| vec_to_point(&p)).collect::<Vec<JsValue>>()
|
||||
self.0.intersect_line_segment(line).iter().map(|&p| vec_to_point(&p)).collect::<Vec<JsValue>>()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user