From af4f57ef38b60456cd027f72dbcbc3f9a7f37cd2 Mon Sep 17 00:00:00 2001 From: Mohamed Osama <67656249+moOsama76@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:23:45 +0200 Subject: [PATCH] Add "Perpendicular to Endpoint" snapping target (#2581) * Perpendicular snap for line's endpoints * move comment * Code review --------- Co-authored-by: Keavon Chambers --- .../portfolio/document/utility_types/misc.rs | 7 ++++ .../snapping/alignment_snapper.rs | 35 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 17612dcd..64633f19 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -99,6 +99,7 @@ impl SnappingState { PathSnapTarget::NormalToPath => self.path.normal_to_path, PathSnapTarget::TangentToPath => self.path.tangent_to_path, PathSnapTarget::IntersectionPoint => self.path.path_intersection_point, + PathSnapTarget::PerpendicularToEndpoint => self.path.perpendicular_from_endpoint, }, SnapTarget::Artboard(_) => self.artboards, SnapTarget::Grid(_) => self.grid_snapping, @@ -142,6 +143,7 @@ pub struct PathSnapping { pub tangent_to_path: bool, pub path_intersection_point: bool, pub align_with_anchor_point: bool, // TODO: Rename + pub perpendicular_from_endpoint: bool, } impl Default for PathSnapping { @@ -154,6 +156,7 @@ impl Default for PathSnapping { tangent_to_path: true, path_intersection_point: true, align_with_anchor_point: true, + perpendicular_from_endpoint: true, } } } @@ -476,6 +479,7 @@ pub enum PathSnapTarget { NormalToPath, TangentToPath, IntersectionPoint, + PerpendicularToEndpoint, } impl fmt::Display for PathSnapTarget { @@ -487,6 +491,7 @@ impl fmt::Display for PathSnapTarget { PathSnapTarget::NormalToPath => write!(f, "Path: Normal to Path"), PathSnapTarget::TangentToPath => write!(f, "Path: Tangent to Path"), PathSnapTarget::IntersectionPoint => write!(f, "Path: Intersection Point"), + PathSnapTarget::PerpendicularToEndpoint => write!(f, "Path: Perp. to Endpoint"), } } } @@ -533,6 +538,7 @@ pub enum AlignmentSnapTarget { ArtboardCenterPoint, AlignWithAnchorPoint, IntersectionPoint, + PerpendicularToEndpoint, } impl fmt::Display for AlignmentSnapTarget { @@ -544,6 +550,7 @@ impl fmt::Display for AlignmentSnapTarget { AlignmentSnapTarget::ArtboardCenterPoint => write!(f, "{}", ArtboardSnapTarget::CenterPoint), AlignmentSnapTarget::AlignWithAnchorPoint => write!(f, "{}", PathSnapTarget::AnchorPointWithColinearHandles), AlignmentSnapTarget::IntersectionPoint => write!(f, "{}", PathSnapTarget::IntersectionPoint), + AlignmentSnapTarget::PerpendicularToEndpoint => write!(f, "{}", PathSnapTarget::PerpendicularToEndpoint), } } } diff --git a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs index 7fa16fe4..48a42351 100644 --- a/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs @@ -59,13 +59,46 @@ impl AlignmentSnapper { // TODO: snap handle points let document = snap_data.document; let tolerance = snap_tolerance(document); - + let tolerance_squared = tolerance.powi(2); let mut snap_x: Option = None; let mut snap_y: Option = None; for target_point in self.bounding_box_points.iter().chain(unselected_geometry) { let target_position = target_point.document_point; + // Perpendicular snap for line's endpoints + if let Some(quad) = target_point.quad.map(|q| q.0) { + if quad[0] == quad[3] && quad[1] == quad[2] && quad[0] == target_point.document_point { + let [p1, p2, ..] = quad; + let direction = (p2 - p1).normalize(); + let normal = DVec2::new(-direction.y, direction.x); + + for endpoint in [p1, p2] { + if let Some(perpendicular_snap) = Quad::intersect_rays(point.document_point, direction, endpoint, normal) { + let distance_squared = point.document_point.distance_squared(perpendicular_snap); + if distance_squared < tolerance_squared { + let distance = distance_squared.sqrt(); + let distance_to_align_target = perpendicular_snap.distance_squared(endpoint).sqrt(); + + let snap_point = SnappedPoint { + snapped_point_document: perpendicular_snap, + source: point.source, + target: SnapTarget::Alignment(AlignmentSnapTarget::PerpendicularToEndpoint), + target_bounds: Some(Quad(quad)), + distance, + tolerance, + distance_to_align_target, + fully_constrained: false, + at_intersection: true, + alignment_target_x: Some(endpoint), + ..Default::default() + }; + snap_results.points.push(snap_point); + } + } + } + } + } let [point_on_x, point_on_y] = if let SnapConstraint::Line { origin, direction } = constraint { [ Quad::intersect_rays(target_point.document_point, DVec2::Y, origin, direction),