Skip to content

Commit 1fb26a1

Browse files
committed
- snap any point on line to tangents instead of snapping endpoint
- remove fully_constrained as per 0hypercube on Discord
1 parent 704dad4 commit 1fb26a1

File tree

2 files changed

+63
-25
lines changed

2 files changed

+63
-25
lines changed

editor/src/messages/tool/common_functionality/snapping/alignment_snapper.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ impl AlignmentSnapper {
8888
distance,
8989
tolerance,
9090
distance_to_align_target,
91-
fully_constrained: false,
9291
at_intersection: true,
9392
alignment_target_x: Some(endpoint),
9493
..Default::default()
@@ -130,7 +129,6 @@ impl AlignmentSnapper {
130129
tolerance,
131130
distance_to_align_target,
132131
alignment_target_x: Some(target_position),
133-
fully_constrained: true,
134132
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
135133
..Default::default()
136134
});
@@ -149,7 +147,6 @@ impl AlignmentSnapper {
149147
tolerance,
150148
distance_to_align_target,
151149
alignment_target_y: Some(target_position),
152-
fully_constrained: true,
153150
at_intersection: matches!(constraint, SnapConstraint::Line { .. }),
154151
..Default::default()
155152
});

editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ impl LayerSnapper {
8989

9090
pub fn free_snap_paths(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, config: SnapTypeConfiguration) {
9191
self.collect_paths(snap_data, !config.use_existing_candidates);
92-
9392
let document = snap_data.document;
9493
let normals = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::NormalToPath));
9594
let tangents = document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::TangentToPath));
@@ -121,8 +120,9 @@ impl LayerSnapper {
121120
..Default::default()
122121
},
123122
});
124-
normals_and_tangents(path, normals, tangents, point, tolerance, snap_results);
123+
snap_normals(path, normals, point, tolerance, snap_results);
125124
}
125+
snap_tangents(path, tangents, point, tolerance, snap_results);
126126
}
127127
}
128128

@@ -262,7 +262,7 @@ impl LayerSnapper {
262262
}
263263
}
264264

265-
fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
265+
fn snap_normals(path: &SnapCandidatePath, normals: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
266266
if normals && path.bounds.is_none() {
267267
for &neighbor in &point.neighbors {
268268
for t in path.document_curve.normals_to_point(neighbor) {
@@ -284,27 +284,68 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
284284
}
285285
}
286286
}
287-
if tangents && path.bounds.is_none() {
288-
for &neighbor in &point.neighbors {
289-
for t in path.document_curve.tangents_to_point(neighbor) {
290-
let tangent_point = path.document_curve.evaluate(TValue::Parametric(t));
291-
let distance = tangent_point.distance(point.document_point);
292-
if distance > tolerance {
293-
continue;
294-
}
295-
snap_results.points.push(SnappedPoint {
296-
snapped_point_document: tangent_point,
297-
target: SnapTarget::Path(PathSnapTarget::TangentToPath),
298-
distance,
299-
tolerance,
300-
outline_layers: [Some(path.layer), None],
301-
source: point.source,
302-
constrained: true,
303-
..Default::default()
304-
});
305-
}
287+
}
288+
289+
// TODO: Snap rectangles and ellipses to ellipses tangents, find out why point.neighbors is empty while drawing rectangles and ellipses
290+
fn snap_tangents(path: &SnapCandidatePath, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
291+
if !tangents || point.neighbors.len() != 1 {
292+
return;
293+
}
294+
let neighbor = point.neighbors[0];
295+
for t in path.document_curve.tangents_to_point(neighbor) {
296+
let tangent_point = path.document_curve.evaluate(TValue::Parametric(t));
297+
298+
if let Some(closest_point) = closest_point_along_line(neighbor, point.document_point, &path.document_curve, tolerance, 20) {
299+
let tangent = (tangent_point - neighbor).normalize();
300+
let offset = (point.document_point - tangent_point).dot(tangent);
301+
let snap_to = tangent_point + tangent * offset;
302+
303+
let distance = snap_to.distance(point.document_point);
304+
snap_results.points.push(SnappedPoint {
305+
snapped_point_document: snap_to,
306+
source: point.source,
307+
target: SnapTarget::Path(PathSnapTarget::TangentToPath),
308+
at_intersection: true,
309+
alignment_target_x: Some(tangent_point),
310+
distance,
311+
tolerance,
312+
outline_layers: [Some(path.layer), None],
313+
target_bounds: Some(Quad([neighbor, neighbor, snap_to, snap_to])),
314+
..Default::default()
315+
});
316+
}
317+
}
318+
}
319+
fn closest_point_along_line(start: DVec2, end: DVec2, curve: &Bezier, tolerance: f64, max_iterations: usize) -> Option<DVec2> {
320+
let mut closest_point = None;
321+
let mut closest_distance = f64::INFINITY;
322+
323+
for i in 0..=max_iterations {
324+
let t = i as f64 / max_iterations as f64;
325+
326+
let curve_point = curve.evaluate(TValue::Parametric(t));
327+
let tangent = curve.tangent(TValue::Parametric(t));
328+
if tangent.length_squared() == 0.0 {
329+
continue;
330+
}
331+
332+
let line_direction = end - start;
333+
if line_direction.length_squared() == 0.0 {
334+
break;
335+
}
336+
337+
let v = curve_point - start;
338+
let projected_distance = v.dot(line_direction.normalize());
339+
let projected_point = start + projected_distance * line_direction.normalize();
340+
341+
let distance = projected_point.distance(curve_point);
342+
343+
if distance < closest_distance {
344+
closest_distance = distance;
345+
closest_point = Some(projected_point);
306346
}
307347
}
348+
if closest_distance < tolerance { closest_point } else { None }
308349
}
309350

310351
#[derive(Clone, Debug)]

0 commit comments

Comments
 (0)