diff --git a/libraries/bezier-rs/src/bezier/solvers.rs b/libraries/bezier-rs/src/bezier/solvers.rs
index c64ad02d..09898be9 100644
--- a/libraries/bezier-rs/src/bezier/solvers.rs
+++ b/libraries/bezier-rs/src/bezier/solvers.rs
@@ -52,15 +52,19 @@ impl Bezier {
}
}
- /// Returns a normalized unit vector representing the tangent at the point `t` along the curve.
- ///
- pub fn tangent(&self, t: TValue) -> DVec2 {
- let t = self.t_value_to_parametric(t);
+ /// Returns the non-normalized vector representing the tangent at the point `t` along the curve.
+ fn non_normalized_tangent(&self, t: f64) -> DVec2 {
match self.handles {
BezierHandles::Linear => self.end - self.start,
_ => self.derivative().unwrap().evaluate(TValue::Parametric(t)),
}
- .normalize()
+ }
+
+ /// Returns a normalized unit vector representing the tangent at the point `t` along the curve.
+ ///
+ pub fn tangent(&self, t: TValue) -> DVec2 {
+ let t = self.t_value_to_parametric(t);
+ self.non_normalized_tangent(t).normalize()
}
/// Returns a normalized unit vector representing the direction of the normal at the point `t` along the curve.
@@ -390,6 +394,15 @@ impl Bezier {
.flat_map(|bezier| self.intersections(bezier, None, None))
.collect()
}
+
+ /// Returns a cubic bezier which joins this with the provided bezier curve.
+ /// The resulting path formed by the Bezier curves is continuous up to the first derivative.
+ ///
+ pub fn join(&self, other: &Bezier) -> Bezier {
+ let handle1 = self.non_normalized_tangent(1.) / 3. + self.end;
+ let handle2 = other.start - other.non_normalized_tangent(0.) / 3.;
+ Bezier::from_cubic_dvec2(self.end, handle1, handle2, other.start)
+ }
}
#[cfg(test)]
diff --git a/website/other/bezier-rs-demos/src/features/bezier-features.ts b/website/other/bezier-rs-demos/src/features/bezier-features.ts
index d25d2ff0..9c4b333e 100644
--- a/website/other/bezier-rs-demos/src/features/bezier-features.ts
+++ b/website/other/bezier-rs-demos/src/features/bezier-features.ts
@@ -483,6 +483,56 @@ const bezierFeatures = {
},
chooseTVariant: true,
},
+ join: {
+ name: "Join",
+ callback: (bezier: WasmBezierInstance): string => {
+ const points = JSON.parse(bezier.get_points());
+ let examplePoints = [];
+ if (points.length === 2) {
+ examplePoints = [
+ [120, 155],
+ [40, 155],
+ ];
+ } else if (points.length === 3) {
+ examplePoints = [
+ [40, 150],
+ [95, 195],
+ [155, 145],
+ ];
+ } else {
+ examplePoints = [
+ [140, 150],
+ [85, 110],
+ [65, 180],
+ [30, 140],
+ ];
+ }
+ return bezier.join(examplePoints);
+ },
+ demoOptions: {
+ Linear: {
+ customPoints: [
+ [45, 40],
+ [130, 90],
+ ],
+ },
+ Quadratic: {
+ customPoints: [
+ [153, 40],
+ [40, 20],
+ [75, 85],
+ ],
+ },
+ Cubic: {
+ customPoints: [
+ [20, 80],
+ [40, 20],
+ [90, 100],
+ [130, 55],
+ ],
+ },
+ },
+ },
};
export type BezierFeatureKey = keyof typeof bezierFeatures;
diff --git a/website/other/bezier-rs-demos/wasm/src/bezier.rs b/website/other/bezier-rs-demos/wasm/src/bezier.rs
index 6f63ca1c..e11999a9 100644
--- a/website/other/bezier-rs-demos/wasm/src/bezier.rs
+++ b/website/other/bezier-rs-demos/wasm/src/bezier.rs
@@ -621,4 +621,44 @@ impl WasmBezier {
.fold(original_curve_svg, |acc, item| format!("{acc}{item}"));
wrap_svg_tag(arcs_svg)
}
+
+ pub fn join(&self, js_points: &JsValue) -> String {
+ let other_bezier: Bezier = match self.0.get_points().count() {
+ 2 => {
+ let points: [DVec2; 2] = serde_wasm_bindgen::from_value(js_points.into()).unwrap();
+ Bezier::from_linear_dvec2(points[0], points[1])
+ }
+ 3 => {
+ let points: [DVec2; 3] = serde_wasm_bindgen::from_value(js_points.into()).unwrap();
+ Bezier::from_quadratic_dvec2(points[0], points[1], points[2])
+ }
+ 4 => {
+ let points: [DVec2; 4] = serde_wasm_bindgen::from_value(js_points.into()).unwrap();
+ Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3])
+ }
+ _ => unreachable!(),
+ };
+
+ let mut other_bezier_svg = String::new();
+ other_bezier.to_svg(
+ &mut other_bezier_svg,
+ CURVE_ATTRIBUTES.to_string().replace(BLACK, GRAY),
+ ANCHOR_ATTRIBUTES.to_string().replace(BLACK, GRAY),
+ String::new(),
+ String::new(),
+ );
+
+ let joining_bezier: Bezier = self.0.join(&other_bezier);
+ let mut joining_bezier_svg = String::new();
+ joining_bezier.to_svg(
+ &mut joining_bezier_svg,
+ CURVE_ATTRIBUTES.to_string().replace(BLACK, RED),
+ ANCHOR_ATTRIBUTES.to_string().replace(BLACK, RED),
+ String::new(),
+ String::new(),
+ );
+
+ let bezier_svg = self.get_bezier_path();
+ wrap_svg_tag(format!("{bezier_svg}{joining_bezier_svg}{other_bezier_svg}"))
+ }
}