diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 50cd60b87b..3899be3534 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1660,65 +1660,241 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, } } + fn calculate_distance_to_spilt(bezier1: bezier_rs::Bezier, bezier2: bezier_rs::Bezier, bevel_length: f64) -> f64 { + if bezier1.is_linear() && bezier2.is_linear() { + let v1 = (bezier1.end - bezier1.start).normalize(); + let v2 = (bezier1.end - bezier2.end).normalize(); + + let dot_product = v1.dot(v2); + + let angle_rad = dot_product.acos(); + + bevel_length / ((angle_rad / 2.0).sin()) + } else { + let length1 = bezier1.length(None); + let length2 = bezier2.length(None); + + let max_split = length1.min(length2); + + let mut split_distance = 0.0; + let mut best_diff = f64::MAX; + let mut current_best_distance = 0.0; + + let clamp_and_round = |value: f64| ((value * 1000.0).round() / 1000.0).clamp(0.0, 1.0); + + const INITIAL_SAMPLES: usize = 50; + for i in 0..=INITIAL_SAMPLES { + let distance_sample = max_split * (i as f64 / INITIAL_SAMPLES as f64); + + let x_point = bezier1.evaluate(TValue::Euclidean(1.0 - clamp_and_round(distance_sample / length1))); + let y_point = bezier2.evaluate(TValue::Euclidean(clamp_and_round(distance_sample / length2))); + + let distance = x_point.distance(y_point); + let diff = (bevel_length - distance).abs(); + + if diff < best_diff { + best_diff = diff; + current_best_distance = distance_sample; + } + + if bevel_length - distance < 0.0 { + split_distance = distance_sample; + + if i > 0 { + let prev_sample = max_split * ((i - 1) as f64 / INITIAL_SAMPLES as f64); + + const REFINE_STEPS: usize = 10; + for j in 1..=REFINE_STEPS { + let refined_sample = prev_sample + (distance_sample - prev_sample) * (j as f64 / REFINE_STEPS as f64); + + let x_point = bezier1.evaluate(TValue::Euclidean(1.0 - (refined_sample / length1).clamp(0.0, 1.0))); + let y_point = bezier2.evaluate(TValue::Euclidean((refined_sample / length2).clamp(0.0, 1.0))); + + let distance = x_point.distance(y_point); + + if bevel_length - distance < 0.0 { + split_distance = refined_sample; + break; + } + } + } + break; + } + } + + if split_distance == 0.0 && current_best_distance > 0.0 { + split_distance = current_best_distance; + } + + split_distance + } + } + fn update_existing_segments(vector_data: &mut VectorData, vector_data_transform: DAffine2, distance: f64, segments_connected: &mut [usize]) -> Vec<[usize; 2]> { let mut next_id = vector_data.point_domain.next_id(); let mut new_segments = Vec::new(); - for (handles, start_point_index, end_point_index) in vector_data.segment_domain.handles_and_points_mut() { - // Convert the original segment to a bezier + let mut first_bezier_bool = true; + + let mut iter = vector_data.segment_domain.handles_and_points_mut(); + + let mut first_bezier = bezier_rs::Bezier { + end: DVec2 { x: 0., y: 0. }, + start: DVec2 { x: 0., y: 0. }, + handles: bezier_rs::BezierHandles::Linear, + }; + + let mut first_start_point_index = &mut 0xffffffff; + let mut first_handles = &mut bezier_rs::BezierHandles::Linear; + + let Some((handles, start_point_index, end_point_index)) = iter.next() else { unreachable!() }; + + let mut prev_bezier = bezier_rs::Bezier { + start: vector_data.point_domain.positions()[*start_point_index], + end: vector_data.point_domain.positions()[*end_point_index], + handles: *handles, + }; + + prev_bezier = prev_bezier.apply_transformation(|p| vector_data_transform.transform_point2(p)); + + let mut prev_handles = handles; + let mut prev_start_point_index = start_point_index; + let mut prev_end_point_index = end_point_index; + let mut prev_original_length = prev_bezier.length(None); + let mut prev_length = prev_original_length; + let mut first_original_length = prev_original_length; + let mut first_length = prev_original_length; + + while let Some((handles, start_point_index, end_point_index)) = iter.next() { let mut bezier = bezier_rs::Bezier { start: vector_data.point_domain.positions()[*start_point_index], end: vector_data.point_domain.positions()[*end_point_index], handles: *handles, }; + bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p)); + + let spilt_distance = calculate_distance_to_spilt(prev_bezier, bezier, distance); + + if prev_bezier.is_linear() { + prev_bezier.handles = bezier_rs::BezierHandles::Linear; + } + if bezier.is_linear() { bezier.handles = bezier_rs::BezierHandles::Linear; } - bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p)); + let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); let original_length = bezier.length(None); let mut length = original_length; + // Only split if the length is big enough to make it worthwhile + let valid_length = prev_length > 1e-10; + if segments_connected[*prev_end_point_index] > 0 && valid_length { + // Apply the bevel to the end + let distance = spilt_distance.min(prev_original_length.min(original_length) / 2.); + prev_bezier = split_distance(prev_bezier.reversed(), distance, prev_length).reversed(); + if first_bezier_bool { + first_length = (first_length - distance).max(0.); + } + // Update the end position + let pos = inverse_transform.transform_point2(prev_bezier.end); + create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, prev_end_point_index, &mut next_id, &mut new_segments); + } + // Update the handles + *prev_handles = prev_bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p)); + // Only split if the length is big enough to make it worthwhile let valid_length = length > 1e-10; if segments_connected[*start_point_index] > 0 && valid_length { // Apply the bevel to the start - let distance = distance.min(original_length / 2.); + let distance = spilt_distance.min(original_length.min(prev_original_length) / 2.); bezier = split_distance(bezier, distance, length); length = (length - distance).max(0.); // Update the start position let pos = inverse_transform.transform_point2(bezier.start); + create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, start_point_index, &mut next_id, &mut new_segments); + + // Update the handles + *handles = bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p)); + } + + if first_bezier_bool { + first_bezier = prev_bezier; + first_start_point_index = prev_start_point_index; + first_handles = prev_handles; + first_original_length = prev_original_length; + first_bezier_bool = false; } + prev_bezier = bezier; + prev_start_point_index = start_point_index; + prev_end_point_index = end_point_index; + prev_handles = handles; + prev_original_length = original_length; + prev_length = length; + } + + if prev_end_point_index == first_start_point_index { + let spilt_distance = calculate_distance_to_spilt(prev_bezier, first_bezier, distance); + + if prev_bezier.is_linear() { + prev_bezier.handles = bezier_rs::BezierHandles::Linear; + } + + if first_bezier.is_linear() { + first_bezier.handles = bezier_rs::BezierHandles::Linear; + } + + let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default(); + // Only split if the length is big enough to make it worthwhile - let valid_length = length > 1e-10; - if segments_connected[*end_point_index] > 0 && valid_length { + let valid_length = prev_length > 1e-10; + if segments_connected[*prev_end_point_index] > 0 && valid_length { // Apply the bevel to the end - let distance = distance.min(original_length / 2.); - bezier = split_distance(bezier.reversed(), distance, length).reversed(); + let distance = spilt_distance.min(prev_original_length.min(first_original_length) / 2.); + prev_bezier = split_distance(prev_bezier.reversed(), distance, prev_length).reversed(); // Update the end position - let pos = inverse_transform.transform_point2(bezier.end); - create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, end_point_index, &mut next_id, &mut new_segments); + let pos = inverse_transform.transform_point2(prev_bezier.end); + create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, prev_end_point_index, &mut next_id, &mut new_segments); } // Update the handles - *handles = bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p)); + *prev_handles = prev_bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p)); + + // Only split if the length is big enough to make it worthwhile + let valid_length = first_length > 1e-10; + if segments_connected[*first_start_point_index] > 0 && valid_length { + // Apply the bevel to the start + let distance = spilt_distance.min(first_original_length.min(prev_original_length) / 2.); + first_bezier = split_distance(first_bezier, distance, first_length); + // Update the start position + let pos = inverse_transform.transform_point2(first_bezier.start); + + create_or_modify_point(&mut vector_data.point_domain, segments_connected, pos, first_start_point_index, &mut next_id, &mut new_segments); + + // Update the handles + *first_handles = first_bezier.handles.apply_transformation(|p| inverse_transform.transform_point2(p)); + } } new_segments } fn insert_new_segments(vector_data: &mut VectorData, new_segments: &[[usize; 2]]) { let mut next_id = vector_data.segment_domain.next_id(); + for &[start, end] in new_segments { - vector_data.segment_domain.push(next_id.next_id(), start, end, bezier_rs::BezierHandles::Linear, StrokeId::ZERO); + let handles = bezier_rs::BezierHandles::Linear; + vector_data.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO); } } - let mut segments_connected = segments_connected_count(&vector_data); - let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected); - insert_new_segments(&mut vector_data, &new_segments); + if distance > 1.0 { + let mut segments_connected = segments_connected_count(&vector_data); + let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected); + insert_new_segments(&mut vector_data, &new_segments); + } vector_data }