Bezier-rs: Convert 'rotate' and 'de casteljau' wasm functions from canvas to SVG rendering (#793)
* Convert rotate demo to svg * Fix bugs in rotate * Fix bugs in rotate * Draft of decasteljau to svg * fixed de casteljau points to_svg impl * clean up wasm impl for de casteljau points * Address Hannah's comments * Nits Co-authored-by: Thomas Cheng <contact.chengthomas@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
4483e98f21
commit
398f6618e4
|
|
@ -27,7 +27,7 @@
|
||||||
import { defineComponent, markRaw } from "vue";
|
import { defineComponent, markRaw } from "vue";
|
||||||
|
|
||||||
import { WasmBezier } from "@/../wasm/pkg";
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
import { drawBezier, drawCircleSector, drawLine, drawPoint, getContextFromCanvas, COLORS } from "@/utils/drawing";
|
import { drawCircleSector, getContextFromCanvas } from "@/utils/drawing";
|
||||||
import { BezierCurveType, CircleSector, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
import { BezierCurveType, CircleSector, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
||||||
|
|
||||||
import BezierExamplePane from "@/components/BezierExamplePane.vue";
|
import BezierExamplePane from "@/components/BezierExamplePane.vue";
|
||||||
|
|
@ -356,41 +356,12 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: "De Casteljau Points",
|
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
|
||||||
const hullPoints: Point[][] = JSON.parse(bezier.de_casteljau_points(options.t));
|
|
||||||
hullPoints.reverse().forEach((iteration: Point[], iterationIndex) => {
|
|
||||||
const colorLight = `hsl(${90 * iterationIndex}, 100%, 50%)`;
|
|
||||||
|
|
||||||
iteration.forEach((point: Point, index) => {
|
|
||||||
// Skip the anchor and handle points which are already drawn in black
|
|
||||||
if (iterationIndex !== hullPoints.length - 1) {
|
|
||||||
drawPoint(getContextFromCanvas(canvas), point, 4, colorLight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index !== 0) {
|
|
||||||
const prevPoint: Point = iteration[index - 1];
|
|
||||||
drawLine(getContextFromCanvas(canvas), point, prevPoint, colorLight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
template: markRaw(SliderExample),
|
|
||||||
templateOptions: { sliders: [tSliderOptions] },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Rotate",
|
name: "Rotate",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI),
|
||||||
const context = getContextFromCanvas(canvas);
|
exampleOptions: {
|
||||||
const rotatedBezier = JSON.parse(bezier.rotate(options.angle * Math.PI).get_points());
|
[BezierCurveType.Quadratic]: {
|
||||||
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
|
sliderOptions: [
|
||||||
},
|
|
||||||
template: markRaw(SliderExample),
|
|
||||||
templateOptions: {
|
|
||||||
sliders: [
|
|
||||||
{
|
{
|
||||||
variable: "angle",
|
variable: "angle",
|
||||||
min: 0,
|
min: 0,
|
||||||
|
|
@ -402,6 +373,18 @@ export default defineComponent({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "De Casteljau Points",
|
||||||
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.de_casteljau_points(options.t),
|
||||||
|
exampleOptions: {
|
||||||
|
[BezierCurveType.Quadratic]: {
|
||||||
|
sliderOptions: [tSliderOptions],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
features: [
|
||||||
{
|
{
|
||||||
name: "Arcs",
|
name: "Arcs",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,6 @@ fn vec_to_point(p: &DVec2) -> Point {
|
||||||
Point { x: p.x, y: p.y }
|
Point { x: p.x, y: p.y }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a bezier to a list of points.
|
|
||||||
fn bezier_to_points(bezier: Bezier) -> Vec<Point> {
|
|
||||||
bezier.get_points().map(|point| Point { x: point.x, y: point.y }).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
||||||
|
|
@ -353,19 +348,52 @@ impl WasmBezier {
|
||||||
wrap_svg_tag(content)
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wrapped return type is `Vec<Vec<Point>>`.
|
pub fn de_casteljau_points(&self, t: f64) -> String {
|
||||||
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
|
let points: Vec<Vec<DVec2>> = self.0.de_casteljau_points(t);
|
||||||
let points: Vec<Vec<Point>> = self
|
|
||||||
.0
|
let bezier_svg = self.get_bezier_path();
|
||||||
.de_casteljau_points(t)
|
|
||||||
|
let casteljau_svg = points
|
||||||
.iter()
|
.iter()
|
||||||
.map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::<Vec<Point>>())
|
.enumerate()
|
||||||
.collect();
|
.map(|(index, points)| {
|
||||||
to_js_value(points)
|
let color_light = format!("hsl({}, 100%, 50%)", 90 * index);
|
||||||
|
let points_and_handle_lines = points
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, point)| {
|
||||||
|
let circle = draw_circle(point.x, point.y, 3., &color_light, 1.5, WHITE);
|
||||||
|
if index != 0 {
|
||||||
|
let prev_point = points[index - 1];
|
||||||
|
let line = draw_line(prev_point.x, prev_point.y, point.x, point.y, &color_light, 1.5);
|
||||||
|
|
||||||
|
circle + line.as_str()
|
||||||
|
} else {
|
||||||
|
circle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fold("".to_string(), |acc, point_svg| acc + &point_svg);
|
||||||
|
points_and_handle_lines
|
||||||
|
})
|
||||||
|
.fold("".to_string(), |acc, points_svg| acc + &points_svg);
|
||||||
|
let content = format!("{bezier_svg}{casteljau_svg}");
|
||||||
|
wrap_svg_tag(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate(&self, angle: f64) -> WasmBezier {
|
// TODO: add support for rotating around point
|
||||||
WasmBezier(self.0.rotate(angle))
|
pub fn rotate(&self, angle: f64) -> String {
|
||||||
|
let original_bezier_svg = self.get_bezier_path();
|
||||||
|
let rotated_bezier = self.0.rotate(angle);
|
||||||
|
let empty_string = String::new();
|
||||||
|
let mut rotated_bezier_svg = String::new();
|
||||||
|
rotated_bezier.to_svg(
|
||||||
|
&mut rotated_bezier_svg,
|
||||||
|
CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
|
||||||
|
empty_string.clone(),
|
||||||
|
empty_string.clone(),
|
||||||
|
empty_string.clone(),
|
||||||
|
);
|
||||||
|
wrap_svg_tag(format!("{original_bezier_svg}{rotated_bezier_svg}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intersect(&self, curve: &Bezier, error: Option<f64>) -> Vec<f64> {
|
fn intersect(&self, curve: &Bezier, error: Option<f64>) -> Vec<f64> {
|
||||||
|
|
@ -478,11 +506,11 @@ impl WasmBezier {
|
||||||
.reduce(None)
|
.reduce(None)
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, bezier_curve)| {
|
.map(|(index, bezier_curve)| {
|
||||||
let mut curve_svg = String::new();
|
let mut curve_svg = String::new();
|
||||||
bezier_curve.to_svg(
|
bezier_curve.to_svg(
|
||||||
&mut curve_svg,
|
&mut curve_svg,
|
||||||
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * idx))),
|
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
|
|
@ -501,11 +529,11 @@ impl WasmBezier {
|
||||||
.offset(distance)
|
.offset(distance)
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, bezier_curve)| {
|
.map(|(index, bezier_curve)| {
|
||||||
let mut curve_svg = String::new();
|
let mut curve_svg = String::new();
|
||||||
bezier_curve.to_svg(
|
bezier_curve.to_svg(
|
||||||
&mut curve_svg,
|
&mut curve_svg,
|
||||||
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * idx))),
|
CURVE_ATTRIBUTES.to_string().replace(BLACK, &format!("hsl({}, 100%, 50%)", (40 * index))),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
empty_string.clone(),
|
empty_string.clone(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue