Skip to content

Fix the Bevel node so it applies a consistent bevel size regardless of angle #2293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 191 additions & 15 deletions node-graph/gcore/src/vector/vector_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down