mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Layer and grid snapping systems (#1521)
* Grid overlays * Rectangle tool basic snapping * Fix bezier demos * Fix bézier crate tests * Constrained snapping for circle & shape tool * Line tool snapping * Pen tool snapping * Path tool snapping * Snapping whilst dragging layers (not constrained) * Constrained drag * Resize snapping * Normal and tangent * Cleanup * Grid snapping * Grid snapping * Fix imports * Fix bug in artboard tool * Fix hang on 0 size grid spacing * Fix NaN when scaling * Polishing --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
78a1bb17cd
commit
456ca170a4
40 changed files with 2170 additions and 475 deletions
|
@ -17,10 +17,13 @@ impl Quad {
|
|||
}
|
||||
|
||||
/// Get all the edges in the quad.
|
||||
pub fn bezier_lines(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
pub fn edges(&self) -> [[DVec2; 2]; 4] {
|
||||
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
|
||||
.into_iter()
|
||||
.map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||
}
|
||||
|
||||
/// Get all the edges in the quad as linear bezier curves
|
||||
pub fn bezier_lines(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
self.edges().into_iter().map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||
}
|
||||
|
||||
/// Generates a [crate::vector::Subpath] of the quad
|
||||
|
@ -66,12 +69,38 @@ impl Quad {
|
|||
pub fn contains(&self, p: DVec2) -> bool {
|
||||
let mut inside = false;
|
||||
for (i, j) in (0..4).zip([3, 0, 1, 2]) {
|
||||
if (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < (self.0[j].x - self.0[i].x * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) {
|
||||
if (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < ((self.0[j].x - self.0[i].x) * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
inside
|
||||
}
|
||||
|
||||
/// https://www.cs.rpi.edu/~cutler/classes/computationalgeometry/F23/lectures/02_line_segment_intersections.pdf
|
||||
fn line_intersection_t(a: DVec2, b: DVec2, c: DVec2, d: DVec2) -> (f64, f64) {
|
||||
let t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x));
|
||||
let u = ((a.x - c.x) * (a.y - b.y) - (a.y - c.y) * (a.x - b.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x));
|
||||
|
||||
(t, u)
|
||||
}
|
||||
|
||||
fn intersect_lines(a: DVec2, b: DVec2, c: DVec2, d: DVec2) -> Option<DVec2> {
|
||||
let (t, u) = Self::line_intersection_t(a, b, c, d);
|
||||
((0. ..=1.).contains(&t) && (0. ..=1.).contains(&u)).then(|| a + t * (b - a))
|
||||
}
|
||||
|
||||
pub fn intersect_rays(a: DVec2, a_direction: DVec2, b: DVec2, b_direction: DVec2) -> Option<DVec2> {
|
||||
let (t, u) = Self::line_intersection_t(a, a + a_direction, b, b + b_direction);
|
||||
(t.is_finite() && u.is_finite()).then(|| a + t * a_direction)
|
||||
}
|
||||
|
||||
pub fn intersects(&self, other: Quad) -> bool {
|
||||
let intersects = self
|
||||
.edges()
|
||||
.into_iter()
|
||||
.any(|[a, b]| other.edges().into_iter().any(|[c, d]| Self::intersect_lines(a, b, c, d).is_some()));
|
||||
self.contains(other.center()) || other.contains(self.center()) || intersects
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul<Quad> for DAffine2 {
|
||||
|
@ -98,9 +127,26 @@ fn offset_quad() {
|
|||
fn quad_contains() {
|
||||
assert!(Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::splat(0.5)));
|
||||
assert!(Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::splat(0.5)));
|
||||
assert!(Quad::from_box([DVec2::splat(300.), DVec2::splat(500.)]).contains(DVec2::splat(350.)));
|
||||
assert!((DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::new(-0.5, 0.5)));
|
||||
|
||||
assert!(!Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::new(1., 1.1)));
|
||||
assert!(!Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::new(0.5, -0.01)));
|
||||
assert!(!(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::splat(0.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_lines() {
|
||||
assert_eq!(
|
||||
Quad::intersect_lines(DVec2::new(-5., 5.), DVec2::new(5., 5.), DVec2::new(2., 7.), DVec2::new(2., 3.)),
|
||||
Some(DVec2::new(2., 5.))
|
||||
);
|
||||
assert_eq!(Quad::intersect_lines(DVec2::new(4., 6.), DVec2::new(4., 5.), DVec2::new(2., 7.), DVec2::new(2., 3.)), None);
|
||||
assert_eq!(Quad::intersect_lines(DVec2::new(-5., 5.), DVec2::new(5., 5.), DVec2::new(2., 7.), DVec2::new(2., 9.)), None);
|
||||
}
|
||||
#[test]
|
||||
fn intersect_quad() {
|
||||
assert!(Quad::from_box([DVec2::ZERO, DVec2::splat(5.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(7.)])));
|
||||
assert!(Quad::from_box([DVec2::ZERO, DVec2::splat(5.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(4.2)])));
|
||||
assert!(!Quad::from_box([DVec2::ZERO, DVec2::splat(3.)]).intersects(Quad::from_box([DVec2::splat(4.), DVec2::splat(4.2)])));
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ use wasm_bindgen::{Clamped, JsCast};
|
|||
use web_sys::window;
|
||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
|
||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||
pub struct Canvas(CanvasRenderingContext2d);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -284,7 +285,10 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
|||
pub use graph_craft::document::value::RenderOutput;
|
||||
pub struct RenderNode<Data, Surface, Parameter> {
|
||||
data: Data,
|
||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||
surface_handle: Surface,
|
||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
||||
surface_handle: PhantomData<Surface>,
|
||||
parameter: PhantomData<Parameter>,
|
||||
}
|
||||
|
||||
|
@ -417,10 +421,13 @@ where
|
|||
}
|
||||
#[automatically_derived]
|
||||
impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
|
||||
pub const fn new(data: Data, surface_handle: Surface) -> Self {
|
||||
pub fn new(data: Data, surface_handle: Surface) -> Self {
|
||||
Self {
|
||||
data,
|
||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||
surface_handle,
|
||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
||||
surface_handle: PhantomData,
|
||||
parameter: PhantomData,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue