jellyflow_runtime/runtime/geometry/paths/
smoothstep.rs1use jellyflow_core::core::CanvasPoint;
2
3use super::super::endpoints::{EdgeEndpointPosition, HandlePosition};
4use super::types::{EdgePath, EdgePathLabel, PathCommand};
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct SmoothStepEdgeOptions {
9 pub offset: f32,
10 pub step_position: f32,
11}
12
13impl Default for SmoothStepEdgeOptions {
14 fn default() -> Self {
15 Self {
16 offset: 20.0,
17 step_position: 0.5,
18 }
19 }
20}
21
22pub fn smoothstep_edge_path(
23 source: EdgeEndpointPosition,
24 target: EdgeEndpointPosition,
25 options: SmoothStepEdgeOptions,
26) -> Option<EdgePath> {
27 if !source.point.is_finite() || !target.point.is_finite() {
28 return None;
29 }
30
31 let options = normalized_smoothstep_options(options);
32 let source_dir = handle_direction(source.position);
33 let target_dir = handle_direction(target.position);
34 let source_gap = translate_point(source.point, source_dir, options.offset)?;
35 let target_gap = translate_point(target.point, target_dir, options.offset)?;
36
37 let mut points = Vec::with_capacity(6);
38 push_distinct_point(&mut points, source.point);
39 push_distinct_point(&mut points, source_gap);
40
41 let horizontal = matches!(
42 source.position,
43 HandlePosition::Left | HandlePosition::Right
44 );
45 let label_point = if horizontal {
46 let center_x = source_gap.x + (target_gap.x - source_gap.x) * options.step_position;
47 push_distinct_point(
48 &mut points,
49 CanvasPoint {
50 x: center_x,
51 y: source_gap.y,
52 },
53 );
54 push_distinct_point(
55 &mut points,
56 CanvasPoint {
57 x: center_x,
58 y: target_gap.y,
59 },
60 );
61 CanvasPoint {
62 x: center_x,
63 y: 0.5 * (source_gap.y + target_gap.y),
64 }
65 } else {
66 let center_y = source_gap.y + (target_gap.y - source_gap.y) * options.step_position;
67 push_distinct_point(
68 &mut points,
69 CanvasPoint {
70 x: source_gap.x,
71 y: center_y,
72 },
73 );
74 push_distinct_point(
75 &mut points,
76 CanvasPoint {
77 x: target_gap.x,
78 y: center_y,
79 },
80 );
81 CanvasPoint {
82 x: 0.5 * (source_gap.x + target_gap.x),
83 y: center_y,
84 }
85 };
86
87 push_distinct_point(&mut points, target_gap);
88 push_distinct_point(&mut points, target.point);
89
90 let mut commands = Vec::with_capacity(points.len());
91 let first = *points.first()?;
92 commands.push(PathCommand::MoveTo(first));
93 for point in points.into_iter().skip(1) {
94 commands.push(PathCommand::LineTo(point));
95 }
96
97 Some(EdgePath {
98 commands,
99 label: EdgePathLabel {
100 point: label_point,
101 offset_x: (label_point.x - source.point.x).abs(),
102 offset_y: (label_point.y - source.point.y).abs(),
103 },
104 })
105}
106
107fn normalized_smoothstep_options(options: SmoothStepEdgeOptions) -> SmoothStepEdgeOptions {
108 SmoothStepEdgeOptions {
109 offset: if options.offset.is_finite() && options.offset >= 0.0 {
110 options.offset
111 } else {
112 SmoothStepEdgeOptions::default().offset
113 },
114 step_position: if options.step_position.is_finite() {
115 options.step_position.clamp(0.0, 1.0)
116 } else {
117 SmoothStepEdgeOptions::default().step_position
118 },
119 }
120}
121
122fn handle_direction(position: HandlePosition) -> (f32, f32) {
123 match position {
124 HandlePosition::Top => (0.0, -1.0),
125 HandlePosition::Right => (1.0, 0.0),
126 HandlePosition::Bottom => (0.0, 1.0),
127 HandlePosition::Left => (-1.0, 0.0),
128 }
129}
130
131fn translate_point(
132 point: CanvasPoint,
133 direction: (f32, f32),
134 distance: f32,
135) -> Option<CanvasPoint> {
136 let out = CanvasPoint {
137 x: point.x + direction.0 * distance,
138 y: point.y + direction.1 * distance,
139 };
140 out.is_finite().then_some(out)
141}
142
143fn push_distinct_point(points: &mut Vec<CanvasPoint>, point: CanvasPoint) {
144 if points.last().is_some_and(|last| *last == point) {
145 return;
146 }
147 points.push(point);
148}