From bfcfe2fd318a553a61783759b783fb06384cf12f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 10 Feb 2021 16:48:05 +0100 Subject: [PATCH] :bug: Fixes problems with path transforms --- .../app/tests/test_common_geom_shapes.clj | 67 ++++++++------ common/app/common/geom/matrix.cljc | 6 ++ common/app/common/geom/shapes/transforms.cljc | 90 ++++++------------- common/app/common/math.cljc | 7 ++ .../app/main/data/workspace/drawing/path.cljs | 9 +- .../app/main/data/workspace/transforms.cljs | 15 +--- 6 files changed, 88 insertions(+), 106 deletions(-) diff --git a/backend/tests/app/tests/test_common_geom_shapes.clj b/backend/tests/app/tests/test_common_geom_shapes.clj index 3048a8903..860f05ea4 100644 --- a/backend/tests/app/tests/test_common_geom_shapes.clj +++ b/backend/tests/app/tests/test_common_geom_shapes.clj @@ -12,6 +12,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.math :refer [close?]] [app.common.pages :refer [make-minimal-shape]] [clojure.test :as t])) @@ -32,7 +33,9 @@ :points points))) (defn add-rect-data [shape] - (let [selrect (gsh/rect->selrect shape) + (let [shape (-> shape + (assoc :width 20 :height 20)) + selrect (gsh/rect->selrect shape) points (gsh/rect->points selrect)] (assoc shape :selrect selrect @@ -64,17 +67,17 @@ shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) - (t/is (== (get-in shape-before [:selrect :x]) - (- 10 (get-in shape-after [:selrect :x])))) + (t/is (close? (get-in shape-before [:selrect :x]) + (- 10 (get-in shape-after [:selrect :x])))) - (t/is (== (get-in shape-before [:selrect :y]) - (+ 10 (get-in shape-after [:selrect :y])))) + (t/is (close? (get-in shape-before [:selrect :y]) + (+ 10 (get-in shape-after [:selrect :y])))) - (t/is (== (get-in shape-before [:selrect :width]) - (get-in shape-after [:selrect :width]))) + (t/is (close? (get-in shape-before [:selrect :width]) + (get-in shape-after [:selrect :width]))) - (t/is (== (get-in shape-before [:selrect :height]) - (get-in shape-after [:selrect :height]))))) + (t/is (close? (get-in shape-before [:selrect :height]) + (get-in shape-after [:selrect :height]))))) :rect :path)) @@ -84,8 +87,8 @@ shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/are [prop] - (t/is (== (get-in shape-before [:selrect prop]) - (get-in shape-after [:selrect prop]))) + (t/is (close? (get-in shape-before [:selrect prop]) + (get-in shape-after [:selrect prop]))) :x :y :width :height :x1 :y1 :x2 :y2)) :rect :path)) @@ -98,17 +101,17 @@ shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) - (t/is (== (get-in shape-before [:selrect :x]) - (get-in shape-after [:selrect :x]))) + (t/is (close? (get-in shape-before [:selrect :x]) + (get-in shape-after [:selrect :x]))) - (t/is (== (get-in shape-before [:selrect :y]) - (get-in shape-after [:selrect :y]))) + (t/is (close? (get-in shape-before [:selrect :y]) + (get-in shape-after [:selrect :y]))) - (t/is (== (* 2 (get-in shape-before [:selrect :width])) - (get-in shape-after [:selrect :width]))) + (t/is (close? (* 2 (get-in shape-before [:selrect :width])) + (get-in shape-after [:selrect :width]))) - (t/is (== (* 2 (get-in shape-before [:selrect :height])) - (get-in shape-after [:selrect :height])))) + (t/is (close? (* 2 (get-in shape-before [:selrect :height])) + (get-in shape-after [:selrect :height])))) :rect :path)) (t/testing "Transform with empty resize" @@ -119,8 +122,8 @@ shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/are [prop] - (t/is (== (get-in shape-before [:selrect prop]) - (get-in shape-after [:selrect prop]))) + (t/is (close? (get-in shape-before [:selrect prop]) + (get-in shape-after [:selrect prop]))) :x :y :width :height :x1 :y1 :x2 :y2)) :rect :path)) @@ -145,13 +148,23 @@ (let [modifiers {:rotation 30} shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] + (t/is (not= shape-before shape-after)) - (t/is (not (== (get-in shape-before [:selrect :x]) - (get-in shape-after [:selrect :x])))) + ;; Selrect won't change with a rotation, but points will + (t/is (close? (get-in shape-before [:selrect :x]) + (get-in shape-after [:selrect :x]))) - (t/is (not (== (get-in shape-before [:selrect :y]) - (get-in shape-after [:selrect :y]))))) + (t/is (close? (get-in shape-before [:selrect :y]) + (get-in shape-after [:selrect :y]))) + + (t/is (= (count (:points shape-before)) (count (:points shape-after)))) + + (for [idx (range 0 (count (:point shape-before)))] + (do (t/is (not (close? (get-in shape-before [:points idx :x]) + (get-in shape-after [:points idx :x])))) + (t/is (not (close? (get-in shape-before [:points idx :y]) + (get-in shape-after [:points idx :y]))))))) :rect :path)) (t/testing "Transform shape with rotation = 0 should leave equal selrect" @@ -160,8 +173,8 @@ shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/are [prop] - (t/is (== (get-in shape-before [:selrect prop]) - (get-in shape-after [:selrect prop]))) + (t/is (close? (get-in shape-before [:selrect prop]) + (get-in shape-after [:selrect prop]))) :x :y :width :height :x1 :y1 :x2 :y2)) :rect :path)) diff --git a/common/app/common/geom/matrix.cljc b/common/app/common/geom/matrix.cljc index e071e32f3..69921998a 100644 --- a/common/app/common/geom/matrix.cljc +++ b/common/app/common/geom/matrix.cljc @@ -134,3 +134,9 @@ (th-eq m1f m2f)))) (defmethod pp/simple-dispatch Matrix [obj] (pr obj)) + +(defn transform-in [pt mtx] + (-> (matrix) + (translate pt) + (multiply mtx) + (translate (gpt/negate pt)))) diff --git a/common/app/common/geom/shapes/transforms.cljc b/common/app/common/geom/shapes/transforms.cljc index e688a16eb..09f021d1e 100644 --- a/common/app/common/geom/shapes/transforms.cljc +++ b/common/app/common/geom/shapes/transforms.cljc @@ -43,10 +43,13 @@ (let [shape-center (or (gco/center-shape shape) (gpt/point 0 0))] (inverse-transform-matrix shape shape-center))) - ([shape center] + ([{:keys [flip-x flip-y] :as shape} center] (let [] (-> (gmt/matrix) (gmt/translate center) + (cond-> + flip-x (gmt/scale (gpt/point -1 1)) + flip-y (gmt/scale (gpt/point 1 -1))) (gmt/multiply (:transform-inverse shape (gmt/matrix))) (gmt/translate (gpt/negate center)))))) @@ -203,29 +206,7 @@ (gmt/rotate (- rotation-angle)))] [stretch-matrix stretch-matrix-inverse])) - -(defn apply-transform-path - [shape transform] - (let [content (gpa/transform-content (:content shape) transform) - - ;; Calculate the new selrect by "unrotate" the shape - rotation (modif-rotation shape) - center (gpt/transform (gco/center-shape shape) transform) - content-rotated (gpa/transform-content content (gmt/rotate-matrix (- rotation) center)) - selrect (gpa/content->selrect content-rotated) - - ;; Transform the points - points (-> (:points shape) - (transform-points transform))] - (assoc shape - :content content - :points points - :selrect selrect - :transform (gmt/rotate-matrix rotation) - :transform-inverse (gmt/rotate-matrix (- rotation)) - :rotation rotation))) - -(defn apply-transform-rect +(defn apply-transform "Given a new set of points transformed, set up the rectangle so it keeps its properties. We adjust de x,y,width,height and create a custom transform" [shape transform] @@ -246,13 +227,21 @@ (:height points-temp-dim)) rect-points (gpr/rect->points rect-shape) - [matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points (:flip-x shape) (:flip-y shape))] + [matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points (:flip-x shape) (:flip-y shape)) + + shape (cond + (= :path (:type shape)) + (-> shape + (update :content #(gpa/transform-content % transform))) + + :else + (-> shape + (merge rect-shape) + (update :x #(mth/precision % 0)) + (update :y #(mth/precision % 0)) + (update :width #(mth/precision % 0)) + (update :height #(mth/precision % 0))))] (as-> shape $ - (merge $ rect-shape) - (update $ :x #(mth/precision % 0)) - (update $ :y #(mth/precision % 0)) - (update $ :width #(mth/precision % 0)) - (update $ :height #(mth/precision % 0)) (update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix)) (update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix)))) (assoc $ :points (into [] points)) @@ -260,37 +249,6 @@ (update $ :rotation #(mod (+ (or % 0) (or (get-in $ [:modifiers :rotation]) 0)) 360))))) -(defn apply-transform [shape transform] - (let [apply-transform-fn - (case (:type shape) - :path apply-transform-path - apply-transform-rect)] - (apply-transform-fn shape transform))) - -(defn transform-gradients [shape modifiers] - (let [angle (d/check-num (get modifiers :rotation)) - ;; Gradients are represented with unit vectors so its center is 0.5, 0.5 - center (gpt/point 0.5 0.5) - transform (gmt/rotate-matrix angle center) - transform-gradient - (fn [{:keys [start-x start-y end-x end-y] :as gradient}] - (let [start-point (gpt/point start-x start-y) - end-point (gpt/point end-x end-y) - {start-x :x start-y :y} (gpt/transform start-point transform) - {end-x :x end-y :y} (gpt/transform end-point transform)] - - (assoc gradient - :start-x start-x - :start-y start-y - :end-x end-x - :end-y end-y)))] - (cond-> shape - (:fill-color-gradient shape) - (update :fill-color-gradient transform-gradient) - - (:stroke-color-gradient shape) - (update :stroke-color-gradient transform-gradient)))) - (defn set-flip [shape modifiers] (let [rx (get-in modifiers [:resize-vector :x]) ry (get-in modifiers [:resize-vector :y])] @@ -305,12 +263,13 @@ (-> shape (set-flip (:modifiers shape)) (apply-transform transform) - (transform-gradients (:modifiers shape)) (dissoc :modifiers))) shape))) (defn update-group-selrect [group children] (let [shape-center (gco/center-shape group) + transform (:transform group (gmt/matrix)) + transform-inverse (:transform-inverse group (gmt/matrix)) ;; Points for every shape inside the group points (->> children (mapcat :points)) @@ -330,5 +289,10 @@ (-> group (assoc :selrect new-selrect) (assoc :points new-points) - (apply-transform-rect (gmt/matrix))))) + + ;; We're regenerating the selrect from its children so we + ;; need to remove the flip flags + (assoc :flip-x false) + (assoc :flip-y false) + (apply-transform (gmt/matrix))))) diff --git a/common/app/common/math.cljc b/common/app/common/math.cljc index e9ed34a29..ecb1c2c6f 100644 --- a/common/app/common/math.cljc +++ b/common/app/common/math.cljc @@ -142,3 +142,10 @@ (defn almost-zero? [num] (< (abs num) 1e-8)) + +(defonce float-equal-precision 0.001) + +(defn close? + "Equality for float numbers. Check if the difference is within a range" + [num1 num2] + (<= (abs (- num1 num2)) float-equal-precision)) diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index 7c69909ed..c73014f1e 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -90,12 +90,15 @@ path))) (defn- points->components [shape content] - (let [rotation (:rotation shape 0) + (let [transform (:transform shape) + transform-inverse (:transform-inverse shape) center (gsh/center-shape shape) - content-rotated (gsh/transform-content content (gmt/rotate-matrix (- rotation) center)) + base-content (gsh/transform-content + content + (gmt/transform-in center transform-inverse)) ;; Calculates the new selrect with points given the old center - points (-> (gsh/content->selrect content-rotated) + points (-> (gsh/content->selrect base-content) (gsh/rect->points) (gsh/transform-points center (:transform shape (gmt/matrix)))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 519517c04..5a772c247 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -82,8 +82,6 @@ {:keys [rotation]} shape shapev (-> (gpt/point width height)) - rotation (if (= :path (:type shape)) 0 rotation) - ;; Vector modifiers depending on the handler handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y)) @@ -125,15 +123,7 @@ ;; lock flag that can be activated on element options. (normalize-proportion-lock [[point shift?]] (let [proportion-lock? (:proportion-lock shape)] - [point (or proportion-lock? shift?)])) - - ;; Applies alginment to point if it is currently - ;; activated on the current workspace - ;; (apply-grid-alignment [point] - ;; (if @refs/selected-alignment - ;; (uwrk/align-point point) - ;; (rx/of point))) - ] + [point (or proportion-lock? shift?)]))] (reify ptk/UpdateEvent (update [_ state] @@ -142,8 +132,7 @@ ptk/WatchEvent (watch [_ state stream] - (let [current-pointer @ms/mouse-position - initial-position (merge current-pointer initial) + (let [initial-position @ms/mouse-position stoper (rx/filter ms/mouse-up? stream) layout (:workspace-layout state) page-id (:current-page-id state)