Skip to main content

jellyflow_runtime/runtime/geometry/paths/
bezier.rs

1use jellyflow_core::core::CanvasPoint;
2
3use super::super::endpoints::{EdgeEndpointPosition, HandlePosition};
4use super::label::bezier_label;
5use super::types::{EdgePath, PathCommand};
6
7/// Bezier edge options.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct BezierEdgeOptions {
10    pub curvature: f32,
11}
12
13impl Default for BezierEdgeOptions {
14    fn default() -> Self {
15        Self { curvature: 0.25 }
16    }
17}
18
19pub fn bezier_edge_path(
20    source: EdgeEndpointPosition,
21    target: EdgeEndpointPosition,
22    options: BezierEdgeOptions,
23) -> Option<EdgePath> {
24    if !source.point.is_finite() || !target.point.is_finite() {
25        return None;
26    }
27
28    let curvature = if options.curvature.is_finite() && options.curvature >= 0.0 {
29        options.curvature
30    } else {
31        BezierEdgeOptions::default().curvature
32    };
33    let control1 = control_with_curvature(source.position, source.point, target.point, curvature);
34    let control2 = control_with_curvature(target.position, target.point, source.point, curvature);
35    let label = bezier_label(source.point, control1, control2, target.point)?;
36
37    Some(EdgePath {
38        commands: vec![
39            PathCommand::MoveTo(source.point),
40            PathCommand::CubicTo {
41                control1,
42                control2,
43                to: target.point,
44            },
45        ],
46        label,
47    })
48}
49
50fn control_with_curvature(
51    position: HandlePosition,
52    from: CanvasPoint,
53    to: CanvasPoint,
54    curvature: f32,
55) -> CanvasPoint {
56    match position {
57        HandlePosition::Left => CanvasPoint {
58            x: from.x - control_offset(from.x - to.x, curvature),
59            y: from.y,
60        },
61        HandlePosition::Right => CanvasPoint {
62            x: from.x + control_offset(to.x - from.x, curvature),
63            y: from.y,
64        },
65        HandlePosition::Top => CanvasPoint {
66            x: from.x,
67            y: from.y - control_offset(from.y - to.y, curvature),
68        },
69        HandlePosition::Bottom => CanvasPoint {
70            x: from.x,
71            y: from.y + control_offset(to.y - from.y, curvature),
72        },
73    }
74}
75
76fn control_offset(distance: f32, curvature: f32) -> f32 {
77    if distance >= 0.0 {
78        0.5 * distance
79    } else {
80        curvature * 25.0 * (-distance).sqrt()
81    }
82}