Fix the Auto-Tangents node for linear polylines, and track colinear handles in manipulator data (#3972)
* Fix the Auto-Tangents node for linear polylines, and track colinear handles in manipulator data * Simplify
This commit is contained in:
parent
71ff4c937f
commit
e2a142333f
|
|
@ -21,8 +21,8 @@ use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
|||
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
||||
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
||||
use vector_types::vector::misc::{
|
||||
CentroidType, ExtrudeJoiningAlgorithm, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment, is_linear,
|
||||
point_to_dvec2, segment_to_handles,
|
||||
CentroidType, ExtrudeJoiningAlgorithm, HandleId, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment,
|
||||
is_linear, point_to_dvec2, segment_to_handles,
|
||||
};
|
||||
use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||
|
|
@ -900,80 +900,118 @@ async fn auto_tangents(
|
|||
}
|
||||
|
||||
let mut new_manipulators_list = Vec::with_capacity(manipulators_list.len());
|
||||
// Track which manipulator indices were given auto-tangent (colinear) handles
|
||||
let mut auto_tangented = vec![false; manipulators_list.len()];
|
||||
let is_closed = subpath.closed();
|
||||
|
||||
for i in 0..manipulators_list.len() {
|
||||
let curr = &manipulators_list[i];
|
||||
let current = &manipulators_list[i];
|
||||
let is_endpoint = !is_closed && (i == 0 || i == manipulators_list.len() - 1);
|
||||
|
||||
if preserve_existing {
|
||||
// Check if this point has handles that are meaningfully different from the anchor
|
||||
let has_handles = (curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5))
|
||||
|| (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5));
|
||||
let has_handles = (current.in_handle.is_some() && !current.in_handle.unwrap().abs_diff_eq(current.anchor, 1e-5))
|
||||
|| (current.out_handle.is_some() && !current.out_handle.unwrap().abs_diff_eq(current.anchor, 1e-5));
|
||||
|
||||
// If the point already has handles, or if it's an endpoint of an open path, keep it as is.
|
||||
if has_handles || (!is_closed && (i == 0 || i == manipulators_list.len() - 1)) {
|
||||
new_manipulators_list.push(*curr);
|
||||
// If the point already has handles, keep it as is
|
||||
if has_handles {
|
||||
new_manipulators_list.push(*current);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If spread is 0, remove handles for this point, making it a sharp corner.
|
||||
// If spread is 0, remove handles for this point, making it a sharp corner
|
||||
if spread == 0. {
|
||||
new_manipulators_list.push(ManipulatorGroup {
|
||||
anchor: curr.anchor,
|
||||
anchor: current.anchor,
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: curr.id,
|
||||
id: current.id,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Endpoints of open paths get zero-length cubic handles so adjacent segments remain cubic (not quadratic)
|
||||
if is_endpoint {
|
||||
new_manipulators_list.push(ManipulatorGroup {
|
||||
anchor: current.anchor,
|
||||
in_handle: Some(current.anchor),
|
||||
out_handle: Some(current.anchor),
|
||||
id: current.id,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get previous and next points for auto-tangent calculation
|
||||
let prev_idx = if i == 0 { if is_closed { manipulators_list.len() - 1 } else { i } } else { i - 1 };
|
||||
let next_idx = if i == manipulators_list.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 };
|
||||
let prev_index = if i == 0 { manipulators_list.len() - 1 } else { i - 1 };
|
||||
let next_index = if i == manipulators_list.len() - 1 { 0 } else { i + 1 };
|
||||
|
||||
let prev = manipulators_list[prev_idx].anchor;
|
||||
let curr_pos = curr.anchor;
|
||||
let next = manipulators_list[next_idx].anchor;
|
||||
let current_position = current.anchor;
|
||||
let delta_prev = manipulators_list[prev_index].anchor - current_position;
|
||||
let delta_next = manipulators_list[next_index].anchor - current_position;
|
||||
|
||||
// Calculate directions from current point to adjacent points
|
||||
let dir_prev = (prev - curr_pos).normalize_or_zero();
|
||||
let dir_next = (next - curr_pos).normalize_or_zero();
|
||||
// Calculate normalized directions and distances to adjacent points
|
||||
let distance_prev = delta_prev.length();
|
||||
let distance_next = delta_next.length();
|
||||
|
||||
// Check if we have valid directions (e.g., points are not coincident)
|
||||
if dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 {
|
||||
if distance_prev < 1e-5 || distance_next < 1e-5 {
|
||||
// Fallback: keep the original manipulator group (which has no active handles here)
|
||||
new_manipulators_list.push(*curr);
|
||||
new_manipulators_list.push(*current);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate handle direction (colinear, pointing along the line from prev to next)
|
||||
// Original logic: (dir_prev - dir_next) is equivalent to (prev - curr) - (next - curr) = prev - next
|
||||
// The handle_dir will be along the line connecting prev and next, or perpendicular if they are coincident.
|
||||
let mut handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or_else(|| dir_prev.perp());
|
||||
let direction_prev = delta_prev / distance_prev;
|
||||
let direction_next = delta_next / distance_next;
|
||||
|
||||
// Ensure consistent orientation of the handle_dir
|
||||
// This makes the `+ handle_dir` for in_handle and `- handle_dir` for out_handle consistent
|
||||
if dir_prev.dot(handle_dir) < 0. {
|
||||
handle_dir = -handle_dir;
|
||||
// Calculate handle direction as the bisector of the two normalized directions.
|
||||
// This ensures the in and out handles are colinear (180° apart) through the anchor.
|
||||
let mut handle_direction = (direction_prev - direction_next).try_normalize().unwrap_or_else(|| direction_prev.perp());
|
||||
|
||||
// Ensure consistent orientation of the handle direction.
|
||||
// This makes the `+ handle_direction` for in_handle and `- handle_direction` for out_handle consistent.
|
||||
if direction_prev.dot(handle_direction) < 0. {
|
||||
handle_direction = -handle_direction;
|
||||
}
|
||||
|
||||
// Calculate handle lengths: 1/3 of distance to adjacent points, scaled by spread
|
||||
let in_length = (curr_pos - prev).length() / 3. * spread;
|
||||
let out_length = (next - curr_pos).length() / 3. * spread;
|
||||
let in_length = distance_prev / 3. * spread;
|
||||
let out_length = distance_next / 3. * spread;
|
||||
|
||||
// Create new manipulator group with calculated auto-tangents
|
||||
new_manipulators_list.push(ManipulatorGroup {
|
||||
anchor: curr_pos,
|
||||
in_handle: Some(curr_pos + handle_dir * in_length),
|
||||
out_handle: Some(curr_pos - handle_dir * out_length),
|
||||
id: curr.id,
|
||||
anchor: current_position,
|
||||
in_handle: Some(current_position + handle_direction * in_length),
|
||||
out_handle: Some(current_position - handle_direction * out_length),
|
||||
id: current.id,
|
||||
});
|
||||
auto_tangented[i] = true;
|
||||
}
|
||||
|
||||
// Record segment count before appending so we can find the new segment IDs
|
||||
let segment_offset = result.segment_domain.ids().len();
|
||||
|
||||
let mut softened_bezpath = bezpath_from_manipulator_groups(&new_manipulators_list, is_closed);
|
||||
softened_bezpath.apply_affine(Affine::new(transform.inverse().to_cols_array()));
|
||||
result.append_bezpath(softened_bezpath);
|
||||
|
||||
// Mark auto-tangented points as having colinear handles
|
||||
let segment_ids = result.segment_domain.ids();
|
||||
let num_manipulators = new_manipulators_list.len();
|
||||
for (i, _) in auto_tangented.iter().enumerate().filter(|&(_, &tangented)| tangented) {
|
||||
// For interior point i, the incoming segment is segment_offset + (i - 1) and outgoing is segment_offset + i.
|
||||
// For closed paths, point 0's incoming segment is the last one (segment_offset + num_manipulators - 1).
|
||||
// For open paths, endpoints are never auto-tangented (the `is_endpoint` check above ensures that),
|
||||
// so `i == 0` and `i == num_manipulators - 1` only occur here when the path is closed
|
||||
let in_segment_index = if i == 0 { segment_offset + num_manipulators - 1 } else { segment_offset + i - 1 };
|
||||
let out_segment_index = if i == num_manipulators - 1 { segment_offset } else { segment_offset + i };
|
||||
|
||||
if in_segment_index < segment_ids.len() && out_segment_index < segment_ids.len() {
|
||||
result
|
||||
.colinear_manipulators
|
||||
.push([HandleId::end(segment_ids[in_segment_index]), HandleId::primary(segment_ids[out_segment_index])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableRow {
|
||||
|
|
|
|||
Loading…
Reference in New Issue