mirror of
https://github.com/penpot/penpot.git
synced 2026-06-20 06:12:04 +00:00
🎉 Implement collision detection with guides
This commit is contained in:
parent
54fb6901f9
commit
3916bfb334
@ -22,12 +22,57 @@ impl GuidePool {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle the unwraps here
|
||||
self.horizontal
|
||||
.sort_by(|a, b| a.position().total_cmp(&b.position()));
|
||||
self.vertical
|
||||
.sort_by(|a, b| a.position().total_cmp(&b.position()));
|
||||
}
|
||||
|
||||
pub fn find_at(&self, x: f32, y: f32, zoom: f32, tolerance: f32) -> Option<&Guide> {
|
||||
if zoom <= 0.0 || tolerance < 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let world_tolerance = tolerance / zoom;
|
||||
let vertical = Self::find_closest_in_axis(&self.vertical, x, world_tolerance);
|
||||
let horizontal = Self::find_closest_in_axis(&self.horizontal, y, world_tolerance);
|
||||
|
||||
match (vertical, horizontal) {
|
||||
(Some(v), Some(h)) => {
|
||||
let v_dist = (v.position() - x).abs();
|
||||
let h_dist = (h.position() - y).abs();
|
||||
if v_dist <= h_dist {
|
||||
Some(v)
|
||||
} else {
|
||||
Some(h)
|
||||
}
|
||||
}
|
||||
(v, h) => v.or(h),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_closest_in_axis(guides: &[Guide], coord: f32, world_tolerance: f32) -> Option<&Guide> {
|
||||
if guides.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let idx = guides.partition_point(|guide| guide.position() < coord);
|
||||
let mut closest: Option<&Guide> = None;
|
||||
let mut closest_dist = world_tolerance;
|
||||
|
||||
for candidate_idx in [idx.wrapping_sub(1), idx] {
|
||||
if candidate_idx < guides.len() {
|
||||
let guide = &guides[candidate_idx];
|
||||
let dist = (guide.position() - coord).abs();
|
||||
if dist <= world_tolerance && dist <= closest_dist {
|
||||
closest_dist = dist;
|
||||
closest = Some(guide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UIState {
|
||||
@ -52,7 +97,78 @@ impl UIState {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn find_guide_at(&self, _x: f32, _y: f32, _zoom: f32) -> Option<&Guide> {
|
||||
unimplemented!("TODO: Implement guide finding");
|
||||
fn find_guide_at(&self, x: f32, y: f32, zoom: f32, tolerance: f32) -> Option<&Guide> {
|
||||
self.guides.find_at(x, y, zoom, tolerance)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::shapes::Color;
|
||||
|
||||
fn vertical_guide(position: f32, index: usize) -> Guide {
|
||||
Guide::new(GuideKind::Vertical(position), Color::BLACK, Some(index))
|
||||
}
|
||||
|
||||
fn horizontal_guide(position: f32, index: usize) -> Guide {
|
||||
Guide::new(GuideKind::Horizontal(position), Color::BLACK, Some(index))
|
||||
}
|
||||
|
||||
fn pool_with(guides: Vec<Guide>) -> GuidePool {
|
||||
let mut pool = GuidePool::new();
|
||||
pool.set(guides);
|
||||
pool
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_returns_none_when_no_guides() {
|
||||
let pool = GuidePool::new();
|
||||
assert!(pool.find_at(100.0, 100.0, 1.0, 8.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_finds_vertical_guide_within_tolerance() {
|
||||
let pool = pool_with(vec![vertical_guide(100.0, 0)]);
|
||||
let guide = pool.find_at(102.0, 50.0, 1.0, 8.0).unwrap();
|
||||
assert_eq!(guide.index, 0);
|
||||
assert_eq!(guide.kind, GuideKind::Vertical(100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_misses_vertical_guide_outside_tolerance() {
|
||||
let pool = pool_with(vec![vertical_guide(100.0, 0)]);
|
||||
assert!(pool.find_at(110.0, 50.0, 1.0, 8.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_finds_horizontal_guide_within_tolerance() {
|
||||
let pool = pool_with(vec![horizontal_guide(200.0, 1)]);
|
||||
let guide = pool.find_at(50.0, 203.0, 1.0, 8.0).unwrap();
|
||||
assert_eq!(guide.index, 1);
|
||||
assert_eq!(guide.kind, GuideKind::Horizontal(200.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_picks_closest_vertical_guide() {
|
||||
let pool = pool_with(vec![vertical_guide(100.0, 0), vertical_guide(105.0, 1)]);
|
||||
let guide = pool.find_at(103.0, 0.0, 1.0, 8.0).unwrap();
|
||||
assert_eq!(guide.index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_prefers_closer_guide_at_intersection() {
|
||||
let pool = pool_with(vec![vertical_guide(102.0, 0), horizontal_guide(100.0, 1)]);
|
||||
let guide = pool.find_at(100.0, 100.0, 1.0, 8.0).unwrap();
|
||||
assert_eq!(guide.index, 1);
|
||||
assert_eq!(guide.kind, GuideKind::Horizontal(100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_at_scales_tolerance_with_zoom() {
|
||||
let pool = pool_with(vec![vertical_guide(100.0, 0)]);
|
||||
|
||||
assert!(pool.find_at(104.0, 0.0, 2.0, 8.0).is_some());
|
||||
assert!(pool.find_at(105.0, 0.0, 2.0, 8.0).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user