From c459c56f37e642edf93eba8fa63c214c96bc1a73 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 5 Jan 2023 15:43:27 +0100 Subject: [PATCH] :zap: Improved performance of snap to distances --- common/src/app/common/geom/shapes.cljc | 23 ++--- common/src/app/common/geom/shapes/rect.cljc | 16 ++-- frontend/src/app/main/snap.cljs | 83 +++++++++++-------- .../ui/workspace/viewport/snap_distances.cljs | 22 ++--- 4 files changed, 73 insertions(+), 71 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 0c2e9043ac..732c2798cd 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -82,24 +82,12 @@ (update :height (comp inc inc)))))) (defn selrect->areas [bounds selrect] - (let [make-selrect - (fn [x1 y1 x2 y2] - (let [x1 (min x1 x2) - x2 (max x1 x2) - y1 (min y1 y2) - y2 (max y1 y2)] - {:x1 x1 :y1 y1 - :x2 x2 :y2 y2 - :x x1 :y y1 - :width (- x2 x1) - :height (- y2 y1) - :type :rect})) - {bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds + (let [{bound-x1 :x1 bound-x2 :x2 bound-y1 :y1 bound-y2 :y2} bounds {sr-x1 :x1 sr-x2 :x2 sr-y1 :y1 sr-y2 :y2} selrect] - {:left (make-selrect bound-x1 sr-y1 sr-x1 sr-y2) - :top (make-selrect sr-x1 bound-y1 sr-x2 sr-y1) - :right (make-selrect sr-x2 sr-y1 bound-x2 sr-y2) - :bottom (make-selrect sr-x1 sr-y2 sr-x2 bound-y2)})) + {:left (gpr/corners->selrect bound-x1 sr-y1 sr-x1 sr-y2) + :top (gpr/corners->selrect sr-x1 bound-y1 sr-x2 sr-y1) + :right (gpr/corners->selrect sr-x2 sr-y1 bound-x2 sr-y2) + :bottom (gpr/corners->selrect sr-x1 sr-y2 sr-x2 bound-y2)})) (defn distance-selrect [selrect other] (let [{:keys [x1 y1]} other @@ -161,6 +149,7 @@ (dm/export gpr/contains-selrect?) (dm/export gpr/contains-point?) (dm/export gpr/close-selrect?) +(dm/export gpr/clip-selrect) (dm/export gtr/move) (dm/export gtr/absolute-move) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index ca400800e2..258ed0cd38 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -212,9 +212,13 @@ (<= (:y2 sr2) (:y2 sr1)))) (defn corners->selrect - [p1 p2] - (let [xp1 (:x p1) - xp2 (:x p2) - yp1 (:y p1) - yp2 (:y p2)] - (make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2))))) + ([p1 p2] + (corners->selrect (:x p1) (:y p1) (:x p2) (:y p2))) + ([xp1 yp1 xp2 yp2] + (make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2))))) + +(defn clip-selrect + [{:keys [x1 y1 x2 y2] :as sr} bounds] + (when (some? sr) + (let [{bx1 :x1 by1 :y1 bx2 :x2 by2 :y2} (rect->selrect bounds)] + (corners->selrect (max bx1 x1) (max by1 y1) (min bx2 x2) (min by2 y2))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 3a602ebf7e..27480b0e80 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -144,17 +144,6 @@ dist-lt (fn [other] (sr-distance coord (:selrect other) selrect)) dist-gt (fn [other] (sr-distance coord selrect (:selrect other))) - ;; Calculates the snap distance when in the middle of two shapes - between-snap - (fn [[sh-lt sh-gt]] - ;; To calculate the middle snap. - ;; Given x, the distance to a left shape and y to a right shape - ;; x - v = y + v => v = (x - y)/2 - ;; v will be the vector that we need to move the shape so it "snaps" - ;; in the middle - (/ (- (dist-gt sh-gt) - (dist-lt sh-lt)) 2)) - ;; Calculates the distance between all the shapes given as argument inner-distance (fn [selrects] @@ -164,14 +153,6 @@ #(overlap? coord %1 %2) #{}))) - best-snap - (fn [acc val] - ;; Using a number is faster than accessing the variable. - ;; Keep up to date with `snap-distance-accuracy` - (if (and (<= val snap-distance-accuracy) (>= val (- snap-distance-accuracy))) - (min acc val) - acc)) - ;; Distance between the elements in an area, these are the snap ;; candidates to either side lt-cand (inner-distance (mapv :selrect shapes-lt)) @@ -182,17 +163,46 @@ lt-dist (into #{} (map dist-lt) shapes-lt) gt-dist (into #{} (map dist-gt) shapes-gt) - ;; Calculate the snaps, we need to reverse depending on area - lt-snap (d/join lt-cand lt-dist -) - gt-snap (d/join gt-dist gt-cand -) + get-side-snaps + (fn [candidates distances] + ;; We add to the range tree the distrances between elements + ;; then, for each distance from the selection we query the tree + ;; to find a snap + (let [range-tree (rt/make-tree) + range-tree + (->> candidates + (reduce #(rt/insert %1 %2 %2) range-tree))] + (->> distances + (mapcat + (fn [cd] + (->> (rt/range-query + range-tree + (- cd snap-distance-accuracy) + (+ cd snap-distance-accuracy)) + (map #(- (first %) cd )))))))) - ;; Calculate snap-between - between-snap (->> (d/join shapes-lt shapes-gt) - (map between-snap)) + get-middle-snaps + (fn [lt-dist gt-dist] + (let [range-tree (rt/make-tree) + range-tree (->> lt-dist + (reduce #(rt/insert %1 %2 %2) range-tree))] + (->> gt-dist + (mapcat (fn [cd] + (->> (rt/range-query + range-tree + (- cd (* snap-distance-accuracy 2)) + (+ cd (* snap-distance-accuracy 2))) + (map #(/ (- cd (first %)) 2)))))))) + + ;; Calculate the snaps, we need to reverse depending on area + lt-snap (get-side-snaps lt-cand lt-dist) + gt-snap (get-side-snaps gt-dist gt-cand) + md-snap (get-middle-snaps lt-dist gt-dist) ;; Search the minimum snap - snap-list (d/concat-vec lt-snap gt-snap between-snap) - min-snap (reduce best-snap ##Inf snap-list)] + snap-list (d/concat-vec lt-snap gt-snap md-snap) + + min-snap (reduce min ##Inf snap-list)] (if (d/num? min-snap) [0 min-snap] nil))) @@ -202,14 +212,14 @@ (calculate-snap coord selrect shapes-lt shapes-gt zoom))))) (defn select-shapes-area - [page-id shapes objects area-selrect] + [page-id frame-id selected objects area] (->> (uw/ask! {:cmd :selection/query :page-id page-id - :frame-id (->> shapes first :frame-id) + :frame-id frame-id :include-frames? true - :rect area-selrect}) + :rect area}) (rx/map #(cph/clean-loops objects %)) - (rx/map #(set/difference % (into #{} (map :id shapes)))) + (rx/map #(set/difference % selected)) (rx/map #(map (d/getf objects) %)))) (defn closest-distance-snap @@ -220,9 +230,14 @@ (->> (rx/of (vector frame selrect)) (rx/merge-map (fn [[frame selrect]] - (let [areas (->> (gsh/selrect->areas (or (:selrect frame) - (gsh/rect->selrect @refs/vbox)) selrect) - (d/mapm #(select-shapes-area page-id shapes objects %2))) + (let [vbox (gsh/rect->selrect @refs/vbox) + frame-id (->> shapes first :frame-id) + selected (into #{} (map :id shapes)) + areas (->> (gsh/selrect->areas + (or (gsh/clip-selrect (:selrect frame) vbox) + vbox) + selrect) + (d/mapm #(select-shapes-area page-id frame-id selected objects %2))) snap-x (search-snap-distance selrect :x (:left areas) (:right areas) zoom) snap-y (search-snap-distance selrect :y (:top areas) (:bottom areas) zoom)] (rx/combine-latest snap-x snap-y)))) diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index 7854fd8c8a..9ae906cc5c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -9,11 +9,10 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] + [app.main.snap :as ams] [app.main.ui.formats :as fmt] - [app.main.worker :as uw] [beicon.core :as rx] [clojure.set :as set] [cuerdas.core :as str] @@ -152,7 +151,7 @@ check-in-set (fn [value number-set] (->> number-set - (some #(<= (mth/abs (- value %)) 0.01)))) + (some #(<= (mth/abs (- value %)) 1.5)))) ;; Left/Top shapes and right/bottom shapes (depends on `coord` parameter) @@ -218,21 +217,16 @@ (fn [[selrect selected frame]] (let [lt-side (if (= coord :x) :left :top) gt-side (if (= coord :x) :right :bottom) - container-selrec (or (:selrect frame) - (gsh/rect->selrect @refs/vbox)) - areas (gsh/selrect->areas container-selrec selrect) + + vbox (gsh/rect->selrect @refs/vbox) + areas (gsh/selrect->areas + (or (gsh/clip-selrect (:selrect frame) vbox) vbox) + selrect) query-side (fn [side] (let [rect (get areas side)] (if (and (> (:width rect) 0) (> (:height rect) 0)) - (->> (uw/ask! {:cmd :selection/query - :page-id page-id - :frame-id (:id frame) - :include-frames? true - :rect rect}) - (rx/map #(cph/clean-loops @refs/workspace-page-objects %)) - (rx/map #(set/difference % selected)) - (rx/map #(->> % (map (partial get @refs/workspace-page-objects))))) + (ams/select-shapes-area page-id (:id frame) selected @refs/workspace-page-objects rect) (rx/of nil))))] (rx/combine-latest (query-side lt-side) (query-side gt-side))))