Use a coarse bounding box to avoid a detailed check for intersection when clicking artwork (#1887)

* Add bounding box check for intersection

* Cache the bounding box

* private subpath
This commit is contained in:
James Lindsay 2024-08-03 15:10:04 +01:00 committed by GitHub
parent 32f5fba3e3
commit ea44d1440a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 27 deletions

View file

@ -23,11 +23,21 @@ use vello::*;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ClickTarget {
pub subpath: bezier_rs::Subpath<PointId>,
pub stroke_width: f64,
subpath: bezier_rs::Subpath<PointId>,
stroke_width: f64,
bounding_box: Option<[DVec2; 2]>,
}
impl ClickTarget {
pub fn new(subpath: bezier_rs::Subpath<PointId>, stroke_width: f64) -> Self {
let bounding_box = subpath.loose_bounding_box();
Self { subpath, stroke_width, bounding_box }
}
pub fn subpath(&self) -> &bezier_rs::Subpath<PointId> {
&self.subpath
}
/// Does the click target intersect the rectangle
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
// Check if the matrix is not invertible
@ -60,9 +70,19 @@ impl ClickTarget {
/// Does the click target intersect the point (accounting for stroke size)
pub fn intersect_point(&self, point: DVec2, layer_transform: DAffine2) -> bool {
let target_bounds = [point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)];
let intersects = |a: [DVec2; 2], b: [DVec2; 2]| a[0].x <= b[1].x && a[1].x >= b[0].x && a[0].y <= b[1].y && a[1].y >= b[0].y;
// This bounding box is not very accurate as it is the axis aligned version of the transformed bounding box. However it is fast.
if !self
.bounding_box
.is_some_and(|loose| intersects((layer_transform * Quad::from_box(loose)).bounding_box(), target_bounds))
{
return false;
}
// Allows for selecting lines
// TODO: actual intersection of stroke
let inflated_quad = Quad::from_box([point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)]);
let inflated_quad = Quad::from_box(target_bounds);
self.intersect_rectangle(inflated_quad, layer_transform)
}
}
@ -343,7 +363,7 @@ impl GraphicElementRendered for VectorData {
}
subpath
};
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
}
#[cfg(feature = "vello")]
@ -558,7 +578,7 @@ impl GraphicElementRendered for Artboard {
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
subpath.apply_transform(self.graphic_group.transform.inverse());
click_targets.push(ClickTarget { stroke_width: 0., subpath });
click_targets.push(ClickTarget::new(subpath, 0.));
}
fn contains_artboard(&self) -> bool {
@ -674,7 +694,7 @@ impl GraphicElementRendered for ImageFrame<Color> {
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
click_targets.push(ClickTarget::new(subpath, 0.));
}
#[cfg(feature = "vello")]