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:
Linda Zheng 2022-11-06 21:34:53 -05:00 committed by Keavon Chambers
parent 4483e98f21
commit 398f6618e4
2 changed files with 73 additions and 62 deletions

View File

@ -27,7 +27,7 @@
import { defineComponent, markRaw } from "vue";
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 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",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
const context = getContextFromCanvas(canvas);
const rotatedBezier = JSON.parse(bezier.rotate(options.angle * Math.PI).get_points());
drawBezier(context, rotatedBezier, null, { curveStrokeColor: COLORS.NON_INTERACTIVE.STROKE_1, radius: 3.5 });
},
template: markRaw(SliderExample),
templateOptions: {
sliders: [
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI),
exampleOptions: {
[BezierCurveType.Quadratic]: {
sliderOptions: [
{
variable: "angle",
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",
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {

View File

@ -39,11 +39,6 @@ fn vec_to_point(p: &DVec2) -> Point {
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.
fn to_js_value<T: Serialize>(data: T) -> JsValue {
JsValue::from_serde(&serde_json::to_string(&data).unwrap()).unwrap()
@ -353,19 +348,52 @@ impl WasmBezier {
wrap_svg_tag(content)
}
/// The wrapped return type is `Vec<Vec<Point>>`.
pub fn de_casteljau_points(&self, t: f64) -> JsValue {
let points: Vec<Vec<Point>> = self
.0
.de_casteljau_points(t)
pub fn de_casteljau_points(&self, t: f64) -> String {
let points: Vec<Vec<DVec2>> = self.0.de_casteljau_points(t);
let bezier_svg = self.get_bezier_path();
let casteljau_svg = points
.iter()
.map(|level| level.iter().map(|&point| Point { x: point.x, y: point.y }).collect::<Vec<Point>>())
.collect();
to_js_value(points)
.enumerate()
.map(|(index, 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 {
WasmBezier(self.0.rotate(angle))
// TODO: add support for rotating around point
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> {
@ -478,11 +506,11 @@ impl WasmBezier {
.reduce(None)
.iter()
.enumerate()
.map(|(idx, bezier_curve)| {
.map(|(index, bezier_curve)| {
let mut curve_svg = String::new();
bezier_curve.to_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(),
@ -501,11 +529,11 @@ impl WasmBezier {
.offset(distance)
.iter()
.enumerate()
.map(|(idx, bezier_curve)| {
.map(|(index, bezier_curve)| {
let mut curve_svg = String::new();
bezier_curve.to_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(),