perf(snap): keep point picking responsive on large drawings
Object snap ran several full scans of the drawing on every cursor move, and the Intersection / Apparent-Intersection passes compare pairs of in-range segments — O(k^2) in the number of wires whose bounding box overlaps the aperture. Zoomed out over a large drawing (or with long lines whose AABB spans the view) that pairwise scan grows to the whole document and freezes point picking, so MOVE/COPY/paste placement hung. Count the in-range wires once and gate only the pairwise passes on it: above the cap they are skipped (you cannot resolve a single intersection when the aperture spans thousands of segments anyway), while every single-wire snap (endpoint, midpoint, centre, perpendicular, nearest) keeps working — so point picking still snaps to corners at any zoom.
This commit is contained in:
parent
30b907cdd6
commit
d0286fbacf
1 changed files with 17 additions and 4 deletions
21
src/snap.rs
21
src/snap.rs
|
|
@ -878,6 +878,19 @@ impl Snapper {
|
||||||
&& cursor_world.y - r <= wire.aabb[3] as f64
|
&& cursor_world.y - r <= wire.aabb[3] as f64
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The per-wire snaps below skip out-of-aperture wires via `wire_in_range`,
|
||||||
|
// so they stay O(n) and snap at any zoom. Intersection / ApparentIntersection
|
||||||
|
// instead compare pairs of in-range segments — O(k²) — which hangs when the
|
||||||
|
// aperture spans the whole drawing. Count the in-range wires once and gate
|
||||||
|
// only those pairwise passes; the single-wire snaps always run.
|
||||||
|
const MAX_PAIRWISE_CANDIDATES: usize = 1_500;
|
||||||
|
let allow_pairwise = wires
|
||||||
|
.iter()
|
||||||
|
.filter(|w| wire_in_range(w))
|
||||||
|
.take(MAX_PAIRWISE_CANDIDATES + 1)
|
||||||
|
.count()
|
||||||
|
<= MAX_PAIRWISE_CANDIDATES;
|
||||||
|
|
||||||
let mut try_pt = |world: glam::DVec3, snap_type: SnapType| {
|
let mut try_pt = |world: glam::DVec3, snap_type: SnapType| {
|
||||||
let screen = world_to_screen(world, view_rot, eye, bounds);
|
let screen = world_to_screen(world, view_rot, eye, bounds);
|
||||||
if !in_bounds(screen) {
|
if !in_bounds(screen) {
|
||||||
|
|
@ -1018,8 +1031,8 @@ impl Snapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Intersection — segment-segment intersections ──────────
|
// ── Intersection — segment-segment intersections (pairwise, gated) ──
|
||||||
if self.is_on(SnapType::Intersection) {
|
if self.is_on(SnapType::Intersection) && allow_pairwise {
|
||||||
for i in 0..wires.len() {
|
for i in 0..wires.len() {
|
||||||
if !wire_in_range(&wires[i]) {
|
if !wire_in_range(&wires[i]) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1169,9 +1182,9 @@ impl Snapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Apparent Intersection — screen-space intersections ─────────────
|
// ── Apparent Intersection — screen-space intersections (pairwise, gated) ──
|
||||||
// L: pre-project each in-range wire's points to screen once, not once per segment pair.
|
// L: pre-project each in-range wire's points to screen once, not once per segment pair.
|
||||||
if self.is_on(SnapType::ApparentIntersection) {
|
if self.is_on(SnapType::ApparentIntersection) && allow_pairwise {
|
||||||
let screen_pts: Vec<Option<Vec<Point>>> = wires
|
let screen_pts: Vec<Option<Vec<Point>>> = wires
|
||||||
.iter()
|
.iter()
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue