From 197eff93e893b5fcaac026bec2cde3c38f5b0196 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 09:59:26 +0100 Subject: [PATCH 01/10] :paperclip: Fix nodejs compatibility issue on uuid_impl --- common/src/app/common/uuid_impl.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js index 5c184f808c..fd257f6ab9 100644 --- a/common/src/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -24,8 +24,10 @@ goog.scope(function() { }; } else if (typeof require === "function") { const crypto = require("crypto"); + const randomBytes = crypto["randomBytes"]; + return (buf) => { - const bytes = crypto.randomBytes(buf.length); + const bytes = randomBytes(buf.length); buf.set(bytes) return buf; }; From f6305db2a8f70103f05e9ab5b31b3ac189bfc938 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:02:23 +0100 Subject: [PATCH 02/10] :sparkles: Reorganize a bit the common.data ns --- common/src/app/common/data.cljc | 186 ++++++++++++++++---------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index d2461675ef..9fa9df4f5d 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -57,9 +57,13 @@ ([a & more] (into (queue) (cons a more)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Data Structures Manipulation +;; Data Structures Access & Manipulation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn not-empty? + [coll] + (boolean (seq coll))) + (defn editable-collection? [m] #?(:clj (instance? clojure.lang.IEditableCollection m) @@ -145,6 +149,16 @@ (rest items) (conj! res [idx (first items)])))))) +(defn group-by + ([kf coll] (group-by kf identity [] coll)) + ([kf vf coll] (group-by kf vf [] coll)) + ([kf vf iv coll] + (let [conj (fnil conj iv)] + (reduce (fn [result item] + (update result (kf item) conj (vf item))) + {} + coll)))) + (defn seek ([pred coll] (seek pred coll nil)) @@ -243,12 +257,12 @@ (defn filterm "Filter values of a map that satisfy a predicate" [pred coll] - (into {} (filter pred coll))) + (into {} (filter pred) coll)) (defn removem "Remove values of a map that satisfy a predicate" [pred coll] - (into {} (remove pred coll))) + (into {} (remove pred) coll)) (defn map-perm "Maps a function to each pair of values that can be combined inside the @@ -373,6 +387,80 @@ (do (vswap! seen conj input*) (rf result input))))))))) +(defn with-next + "Given a collection will return a new collection where each element + is paired with the next item in the collection + (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]" + [coll] + (map vector + coll + (c/concat (rest coll) [nil]))) + +(defn with-prev + "Given a collection will return a new collection where each element + is paired with the previous item in the collection + (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]" + [coll] + (map vector + coll + (c/cons nil coll))) + +(defn with-prev-next + "Given a collection will return a new collection where every item is paired + with the previous and the next item of a collection + (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]" + [coll] + (map vector + coll + (c/cons nil coll) + (c/concat (rest coll) [nil]))) + +(defn deep-mapm + "Applies a map function to an associative map and recurses over its children + when it's a vector or a map" + [mfn m] + (let [do-map + (fn [entry] + (let [[k v] (mfn entry)] + (cond + (or (vector? v) (map? v)) + [k (deep-mapm mfn v)] + + :else + (mfn [k v]))))] + (cond + (map? m) + (into {} (map do-map) m) + + (vector? m) + (into [] (map (partial deep-mapm mfn)) m) + + :else + m))) + +(defn iteration + "Creates a totally lazy seqable via repeated calls to step, a + function of some (continuation token) 'k'. The first call to step + will be passed initk, returning 'ret'. If (somef ret) is true, (vf + ret) will be included in the iteration, else iteration will + terminate and vf/kf will not be called. If (kf ret) is non-nil it + will be passed to the next step call, else iteration will terminate. + + This can be used e.g. to consume APIs that return paginated or batched data. + + step - (possibly impure) fn of 'k' -> 'ret' + :somef - fn of 'ret' -> logical true/false, default 'some?' + :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity' + :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity' + :initk - the first value passed to step, default 'nil' + + It is presumed that step with non-initk is + unreproducible/non-idempotent. If step with initk is unreproducible + it is on the consumer to not consume twice." + [& args] + (->> (apply c/iteration args) + (concat-all))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -439,8 +527,9 @@ (or val default)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Data Parsing / Conversion +;; Utilities ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn nilf "Returns a new function that if you pass nil as any argument will return nil" @@ -494,34 +583,6 @@ (or default-value (str maybe-keyword))))) -(defn with-next - "Given a collection will return a new collection where each element - is paired with the next item in the collection - (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]" - [coll] - (map vector - coll - (c/concat (rest coll) [nil]))) - -(defn with-prev - "Given a collection will return a new collection where each element - is paired with the previous item in the collection - (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]" - [coll] - (map vector - coll - (c/concat [nil] coll))) - -(defn with-prev-next - "Given a collection will return a new collection where every item is paired - with the previous and the next item of a collection - (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]" - [coll] - (map vector - coll - (c/concat [nil] coll) - (c/concat (rest coll) [nil]))) - (defn prefix-keyword "Given a keyword and a prefix will return a new keyword with the prefix attached (prefix-keyword \"prefix\" :test) => :prefix-test" @@ -612,33 +673,6 @@ (recur (inc counter)) candidate)))))))) -(defn deep-mapm - "Applies a map function to an associative map and recurses over its children - when it's a vector or a map" - [mfn m] - (let [do-map - (fn [entry] - (let [[k v] (mfn entry)] - (cond - (or (vector? v) (map? v)) - [k (deep-mapm mfn v)] - - :else - (mfn [k v]))))] - (cond - (map? m) - (into {} (map do-map) m) - - (vector? m) - (into [] (map (partial deep-mapm mfn)) m) - - :else - m))) - -(defn not-empty? - [coll] - (boolean (seq coll))) - (defn kebab-keys [m] (->> m (deep-mapm @@ -647,40 +681,6 @@ [(keyword (str/kebab (name k))) v] [k v]))))) - -(defn group-by - ([kf coll] (group-by kf identity [] coll)) - ([kf vf coll] (group-by kf vf [] coll)) - ([kf vf iv coll] - (let [conj (fnil conj iv)] - (reduce (fn [result item] - (update result (kf item) conj (vf item))) - {} - coll)))) - -(defn iteration - "Creates a totally lazy seqable via repeated calls to step, a - function of some (continuation token) 'k'. The first call to step - will be passed initk, returning 'ret'. If (somef ret) is true, (vf - ret) will be included in the iteration, else iteration will - terminate and vf/kf will not be called. If (kf ret) is non-nil it - will be passed to the next step call, else iteration will terminate. - - This can be used e.g. to consume APIs that return paginated or batched data. - - step - (possibly impure) fn of 'k' -> 'ret' - :somef - fn of 'ret' -> logical true/false, default 'some?' - :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity' - :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity' - :initk - the first value passed to step, default 'nil' - - It is presumed that step with non-initk is - unreproducible/non-idempotent. If step with initk is unreproducible - it is on the consumer to not consume twice." - [& args] - (->> (apply c/iteration args) - (concat-all))) - (defn toggle-selection ([set value] (toggle-selection set value false)) From 89a19dec5b97047bd1e4b907179d524cdd76c107 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:24:31 +0100 Subject: [PATCH 03/10] :tada: Add cljs optimized get-prop helper macro --- common/src/app/common/data.cljc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 9fa9df4f5d..ff7d98f281 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -461,6 +461,15 @@ (->> (apply c/iteration args) (concat-all))) +(defmacro get-prop + "A macro based, optimized variant of `get` that access the property + directly on CLJS, on CLJ works as get." + [obj prop] + (if (:ns &env) + (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) + `(c/get ~obj ~prop))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 380cba3a72025e37edd57c0d61e983c4a615c6b8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:25:36 +0100 Subject: [PATCH 04/10] :paperclip: Add bench namespace to fronend/dev --- frontend/dev/bench.cljs | 48 ++++++++++++++++++++++++++++++++++++++++ frontend/shadow-cljs.edn | 4 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 frontend/dev/bench.cljs diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs new file mode 100644 index 0000000000..cf2a406328 --- /dev/null +++ b/frontend/dev/bench.cljs @@ -0,0 +1,48 @@ +(ns bench + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gsr] + [app.common.perf :as perf] + [clojure.spec.alpha :as s] + [clojure.test.check.generators :as gen])) + +(def points + (gen/sample (s/gen ::gpt/point) 20)) + +(defn points->rect + [points] + (when-let [points (seq points)] + (loop [minx ##Inf + miny ##Inf + maxx ##-Inf + maxy ##-Inf + pts points] + (if-let [pt ^boolean (first pts)] + (let [x (d/get-prop pt :x) + y (d/get-prop pt :y)] + (recur (min minx x) + (min miny y) + (max maxx x) + (max maxy y) + (rest pts))) + (when (d/num? minx miny maxx maxy) + (gsr/make-rect minx miny (- maxx minx) (- maxy miny))))))) + +(defn bench-points + [] + (perf/benchmark + :f #(gsr/points->rect points) + :name "base") + (perf/benchmark + :f #(points->rect points) + :name "optimized")) + + +(defn main + [& [name]] + (case name + "points" (bench-points) + (println "available: points"))) + + diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index dd5285eac1..bb641ad0ce 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -69,10 +69,10 @@ {:target :node-script :output-to "target/bench.js" :output-dir "target/bench/" - :main cljs.user/main + :main bench/main :compiler-options - {:output-feature-set :es8 + {:output-feature-set :es2020 :output-wrapper false :warnings {:fn-deprecated false}} From c28534555baa023647d460ca01e662ca575e117f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 11:08:15 +0100 Subject: [PATCH 05/10] :paperclip: Add minor microptimizations and tests to points->rect --- common/src/app/common/data.cljc | 34 ++++++++++++------- common/src/app/common/geom/shapes/rect.cljc | 23 +++++++++---- common/src/app/common/math.cljc | 5 ++- common/test/common_tests/data_test.cljc | 14 ++++++++ .../test/common_tests/geom_shapes_test.cljc | 23 +++++++++++-- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index ff7d98f281..03fe97ca38 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -555,22 +555,32 @@ (defn num? "Checks if a value `val` is a number but not an Infinite or NaN" - ([val] - (and (number? val) - (mth/finite? val) - (not (mth/nan? val)))) - - ([val & vals] - (and (num? val) - (->> vals (every? num?))))) + ([a] + (mth/finite? a)) + ([a b] + (and (mth/finite? a) + (mth/finite? b))) + ([a b c] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c))) + ([a b c d] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c) + (mth/finite? d))) + ([a b c d & others] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c) + (mth/finite? d) + (every? mth/finite? others)))) (defn check-num "Function that checks if a number is nil or nan. Will return 0 when not valid and the number otherwise." - ([v] - (check-num v 0)) - ([v default] - (if (num? v) v default))) + ([v] (mth/finite v 0)) + ([v default] (mth/finite v default))) (defn any-key? [element & rest] (some #(contains? element %) rest)) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 672928a61c..057687e1e1 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -83,13 +83,22 @@ (defn points->rect [points] - (when (d/not-empty? points) - (let [minx (transduce (keep :x) min ##Inf points) - miny (transduce (keep :y) min ##Inf points) - maxx (transduce (keep :x) max ##-Inf points) - maxy (transduce (keep :y) max ##-Inf points)] - (when (d/num? minx miny maxx maxy) - (make-rect minx miny (- maxx minx) (- maxy miny)))))) + (when-let [points (seq points)] + (loop [minx ##Inf + miny ##Inf + maxx ##-Inf + maxy ##-Inf + pts points] + (if-let [pt (first pts)] + (let [x (d/get-prop pt :x) + y (d/get-prop pt :y)] + (recur (min minx x) + (min miny y) + (max maxx x) + (max maxy y) + (rest pts))) + (when (d/num? minx miny maxx maxy) + (make-rect minx miny (- maxx minx) (- maxy miny))))))) (defn bounds->rect [[{ax :x ay :y} {bx :x by :y} {cx :x cy :y} {dx :x dy :y}]] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index e5483b1189..eee928bd95 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -19,10 +19,13 @@ #?(:cljs (js/isNaN v) :clj (Double/isNaN v))) +;; NOTE: on cljs we don't need to check for `number?` so we explicitly +;; ommit it for performance reasons. + (defn finite? [v] #?(:cljs (and (not (nil? v)) (js/isFinite v)) - :clj (and (not (nil? v)) (Double/isFinite v)))) + :clj (and (not (nil? v)) (number? v) (Double/isFinite v)))) (defn finite [v default] diff --git a/common/test/common_tests/data_test.cljc b/common/test/common_tests/data_test.cljc index 5fd35b9200..0c0521003c 100644 --- a/common/test/common_tests/data_test.cljc +++ b/common/test/common_tests/data_test.cljc @@ -51,3 +51,17 @@ (t/is (= [1 10 100 2 20 200 3 30 300] (d/join [1 2 3] [1 10 100] *)))) +(t/deftest num-predicate + (t/is (not (d/num? ##NaN))) + (t/is (not (d/num? nil))) + (t/is (d/num? 1)) + (t/is (d/num? -0.3)) + (t/is (not (d/num? {})))) + +(t/deftest check-num-helper + (t/is (= 1 (d/check-num 1 0))) + (t/is (= 0 (d/check-num ##NaN 0))) + (t/is (= 0 (d/check-num {} 0))) + (t/is (= 0 (d/check-num [] 0))) + (t/is (= 0 (d/check-num :foo 0))) + (t/is (= 0 (d/check-num nil 0)))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index 864bf6b8c8..a9d1e2df74 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -21,7 +21,8 @@ {:command :curve-to :params {:x 40 :y 40 :c1x 35 :c1y 35 :c2x 45 :c2y 45}} {:command :close-path}]) -(defn add-path-data [shape] +(defn add-path-data + [shape] (let [content (:content shape default-path) selrect (gsh/content->selrect content) points (gsh/rect->points selrect)] @@ -30,7 +31,8 @@ :selrect selrect :points points))) -(defn add-rect-data [shape] +(defn add-rect-data + [shape] (let [shape (-> shape (assoc :width 20 :height 20)) selrect (gsh/rect->selrect shape) @@ -49,7 +51,7 @@ (not= type :path) (add-rect-data))))) -(t/deftest transform-shape-tests +(t/deftest transform-shapes (t/testing "Shape without modifiers should stay the same" (t/are [type] (let [shape-before (create-test-shape type) @@ -181,3 +183,18 @@ :path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} :rect nil :path nil))) + +(t/deftest points-to-selrect + (let [points [(gpt/point 0.5 0.5) + (gpt/point -1 -2) + (gpt/point 20 65.2) + (gpt/point 12 -10)] + result (gsh/points->rect points) + expect {:x -1, :y -10, :width 21, :height 75.2}] + + (t/is (= (:x expect) (:x result))) + (t/is (= (:y expect) (:y result))) + (t/is (= (:width expect) (:width result))) + (t/is (= (:height expect) (:height result))) + )) + From fc4e755f2b19b136efd9a5c873c6438600d0774b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 20 Nov 2022 20:05:15 +0100 Subject: [PATCH 06/10] :zap: Optimize point functions --- common/src/app/common/data.cljc | 8 - common/src/app/common/data/macros.cljc | 12 + common/src/app/common/geom/point.cljc | 363 +++++++++++------- .../common/geom/shapes/pixel_precision.cljc | 4 +- common/src/app/common/geom/shapes/rect.cljc | 5 +- common/test/common_tests/geom_point_test.cljc | 295 ++++++++++++++ frontend/src/app/main/snap.cljs | 4 +- .../frontend_tests/test_helpers_shapes.cljs | 53 +++ 8 files changed, 589 insertions(+), 155 deletions(-) create mode 100644 common/test/common_tests/geom_point_test.cljc create mode 100644 frontend/test/frontend_tests/test_helpers_shapes.cljs diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 03fe97ca38..ae52003c18 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -461,14 +461,6 @@ (->> (apply c/iteration args) (concat-all))) -(defmacro get-prop - "A macro based, optimized variant of `get` that access the property - directly on CLJS, on CLJ works as get." - [obj prop] - (if (:ns &env) - (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) - `(c/get ~obj ~prop))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion diff --git a/common/src/app/common/data/macros.cljc b/common/src/app/common/data/macros.cljc index 0d204e7efa..76a168459d 100644 --- a/common/src/app/common/data/macros.cljc +++ b/common/src/app/common/data/macros.cljc @@ -107,3 +107,15 @@ (d/close! ~(first bindings)))))) `(do ~@body) (reverse (partition 2 bindings)))) + +(defmacro get-prop + "A macro based, optimized variant of `get` that access the property + directly on CLJS, on CLJ works as get." + [obj prop] + ;; `(do + ;; (when-not (record? ~obj) + ;; (js/console.trace (pr-str ~obj))) + ;; (c/get ~obj ~prop))) + (if (:ns &env) + (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) + `(c/get ~obj ~prop))) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 5bc1d4e5e5..5421af3cef 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -11,6 +11,8 @@ :clj [clojure.pprint :as pp]) #?(:cljs [cljs.core :as c] :clj [clojure.core :as c]) + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.math :as mth] [app.common.spec :as us] [clojure.spec.alpha :as s] @@ -20,18 +22,20 @@ (defrecord Point [x y]) -(defn s [{:keys [x y]}] (str "(" x "," y ")")) +(defn s + [pt] + (dm/str "(" (dm/get-prop pt :x) "," (dm/get-prop pt :y) ")")) (defn point? "Return true if `v` is Point instance." [v] - (or (instance? Point v) - (and (map? v) (contains? v :x) (contains? v :y)))) + (instance? Point v)) (s/def ::x ::us/safe-number) (s/def ::y ::us/safe-number) -(s/def ::point-attrs (s/keys :req-un [::x ::y])) +(s/def ::point-attrs + (s/keys :req-un [::x ::y])) (s/def ::point (s/with-gen (s/and ::point-attrs point?) @@ -40,10 +44,8 @@ (defn point-like? [{:keys [x y] :as v}] (and (map? v) - (not (nil? x)) - (not (nil? y)) - (number? x) - (number? y))) + (d/num? x) + (d/num? y))) (defn point "Create a Point instance." @@ -51,13 +53,13 @@ ([v] (cond (point? v) - (Point. (:x v) (:y v)) + v (number? v) (point v v) (point-like? v) - (point (:x v) (:y v)) + (map->Point v) :else (throw (ex-info "Invalid arguments" {:v v})))) @@ -66,128 +68,178 @@ (defn close? [p1 p2] - (and (mth/close? (:x p1) (:x p2)) - (mth/close? (:y p1) (:y p2)))) + (and (mth/close? (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (mth/close? (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) -(defn angle->point [{:keys [x y]} angle distance] +(defn angle->point + [pt angle distance] (point - (+ x (* distance (mth/cos angle))) - (- y (* distance (mth/sin angle))))) + (+ (dm/get-prop pt :x) (* distance (mth/cos angle))) + (- (dm/get-prop pt :y) (* distance (mth/sin angle))))) (defn add "Returns the addition of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (+ x ox) (+ y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (+ (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (+ (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn subtract "Returns the subtraction of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (- x ox) (- y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn multiply "Returns the subtraction of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (* x ox) (* y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn divide - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (/ x ox) (/ y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (/ (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (/ (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn min - ([] (min nil nil)) - ([p1] (min p1 nil)) - ([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + ([] nil) + ([p1] p1) + ([p1 p2] (cond (nil? p1) p2 (nil? p2) p1 - :else (Point. (c/min x1 x2) (c/min y1 y2))))) - + :else (Point. (c/min (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (c/min (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))))) (defn max - ([] (max nil nil)) - ([p1] (max p1 nil)) - ([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + ([] nil) + ([p1] p1) + ([p1 p2] (cond (nil? p1) p2 (nil? p2) p1 - :else (Point. (c/max x1 x2) (c/max y1 y2))))) - + :else (Point. (c/max (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (c/max (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))))) (defn inverse - [{:keys [x y] :as p}] - (assert (point? p)) - (Point. (/ 1 x) (/ 1 y))) + [pt] + (assert (point? pt) "point instance expected") + (Point. (/ 1.0 (dm/get-prop pt :x)) + (/ 1.0 (dm/get-prop pt :y)))) (defn negate - [{x :x y :y :as p}] - (assert (point? p)) - (Point. (- x) (- y))) + [pt] + (assert (point? pt) "point instance expected") + (Point. (- (dm/get-prop pt :x)) + (- (dm/get-prop pt :y)))) (defn distance "Calculate the distance between two points." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (let [dx (- x ox) - dy (- y oy)] + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [dx (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + dy (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y))] (mth/sqrt (+ (mth/pow dx 2) (mth/pow dy 2))))) (defn distance-vector "Calculate the distance, separated x and y." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (let [dx (mth/abs (- x ox)) - dy (mth/abs (- y oy))] - (Point. dx dy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [dx (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + dy (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y))] + (Point. (mth/abs dx) + (mth/abs dy)))) (defn length - [{x :x y :y :as p}] - (assert (point? p)) - (mth/sqrt (+ (mth/pow x 2) - (mth/pow y 2)))) + [pt] + (assert (point? pt) "point instance expected") + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] + (mth/sqrt (+ (mth/pow x 2) + (mth/pow y 2))))) (defn angle "Returns the smaller angle between two vectors. If the second vector is not provided, the angle will be measured from x-axis." - ([{x :x y :y :as p}] - (-> (mth/atan2 y x) - (mth/degrees))) - ([p center] - (angle (subtract p center)))) + ([pt] + (assert (point? pt) "point instance expected") + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] + (-> (mth/atan2 y x) + (mth/degrees)))) + ([pt center] + (assert (point? pt) "point instance expected") + (assert (point? center) "point instance expected") + (let [x (- (dm/get-prop pt :x) + (dm/get-prop center :x)) + y (- (dm/get-prop pt :y) + (dm/get-prop center :y))] + (-> (mth/atan2 y x) + (mth/degrees))))) (defn angle-with-other "Consider point as vector and calculate the angle between two vectors." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - - (let [length-p (length p) - length-other (length other)] - (if (or (mth/almost-zero? length-p) - (mth/almost-zero? length-other)) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [length-p1 (length p1) + length-p2 (length p2)] + (if (or (mth/almost-zero? length-p1) + (mth/almost-zero? length-p2)) 0 - (let [a (/ (+ (* x ox) - (* y oy)) - (* length-p length-other)) + (let [a (/ (+ (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y))) + (* length-p1 length-p2)) a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a))) d (mth/degrees a)] (if (mth/nan? d) 0 d))))) -(defn angle-sign [v1 v2] - (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)) +(defn angle-sign + [p1 p2] + (if (> (* (dm/get-prop p1 :y) (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :x) (dm/get-prop p2 :y))) + -1 + 1)) (defn signed-angle-with-other [v1 v2] @@ -196,61 +248,79 @@ (defn update-angle "Update the angle of the point." [p angle] - (assert (point? p)) - (assert (number? angle)) - (let [len (length p) + (assert (number? angle) "expected number") + (let [len (length p) angle (mth/radians angle)] (Point. (* (mth/cos angle) len) (* (mth/sin angle) len)))) (defn quadrant "Return the quadrant of the angle of the point." - [{:keys [x y] :as p}] - (assert (point? p)) - (if (>= x 0) - (if (>= y 0) 1 4) - (if (>= y 0) 2 3))) + [p] + (assert (point? p) "expected point instance") + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y)] + (if (>= x 0) + (if (>= y 0) 1 4) + (if (>= y 0) 2 3)))) (defn round "Round the coordinates of the point to a precision" ([point] (round point 0)) - ([{:keys [x y] :as p} decimals] - (assert (point? p)) - (assert (number? decimals)) - (Point. (mth/precision x decimals) - (mth/precision y decimals)))) + ([pt decimals] + (assert (point? pt) "expected point instance") + (assert (number? decimals) "expected number instance") + (Point. (mth/precision (dm/get-prop pt :x) decimals) + (mth/precision (dm/get-prop pt :y) decimals)))) (defn half-round "Round the coordinates to the closest half-point" - [{:keys [x y] :as p}] - (assert (point? p)) - (Point. (mth/half-round x) - (mth/half-round y))) + [pt] + (assert (point? pt) "expected point instance") + (Point. (mth/half-round (dm/get-prop pt :x)) + (mth/half-round (dm/get-prop pt :y)))) (defn transform "Transform a point applying a matrix transformation." - [{:keys [x y] :as p} {:keys [a b c d e f]}] - (assert (point? p)) - (Point. (+ (* x a) (* y c) e) - (+ (* x b) (* y d) f))) + [p m] + (when (point? p) + (if (nil? m) + p + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y) + a (dm/get-prop m :a) + b (dm/get-prop m :b) + c (dm/get-prop m :c) + d (dm/get-prop m :d) + e (dm/get-prop m :e) + f (dm/get-prop m :f)] + (Point. (+ (* x a) (* y c) e) + (+ (* x b) (* y d) f)))))) + ;; Vector functions (defn to-vec [p1 p2] (subtract p2 p1)) -(defn scale [v scalar] - (-> v - (update :x * scalar) - (update :y * scalar))) +(defn scale + [p scalar] + (Point. (* (dm/get-prop p :x) scalar) + (* (dm/get-prop p :y) scalar))) -(defn dot [{x1 :x y1 :y} {x2 :x y2 :y}] - (+ (* x1 x2) (* y1 y2))) +(defn dot + [p1 p2] + (+ (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) -(defn unit [v] - (let [v-length (length v)] - (divide v (point v-length v-length)))) +(defn unit + [p1] + (let [p-length (length p1)] + (Point. (/ (dm/get-prop p1 :x) p-length) + (/ (dm/get-prop p1 :y) p-length)))) (defn perpendicular [{:keys [x y]}] @@ -259,7 +329,7 @@ (defn project "V1 perpendicular projection on vector V2" [v1 v2] - (let [v2-unit (unit v2) + (let [v2-unit (unit v2) scalar-proj (dot v1 v2-unit)] (scale v2-unit scalar-proj))) @@ -282,43 +352,53 @@ (defn point-line-distance "Returns the distance from a point to a line defined by two points" [point line-point1 line-point2] - (let [{x0 :x y0 :y} point - {x1 :x y1 :y} line-point1 - {x2 :x y2 :y} line-point2 - num (mth/abs - (+ (* x0 (- y2 y1)) - (- (* y0 (- x2 x1))) - (* x2 y1) - (- (* y2 x1)))) - dist (distance line-point2 line-point1)] - (/ num dist))) + (let [x0 (dm/get-prop point :x) + y0 (dm/get-prop point :y) + x1 (dm/get-prop line-point1 :x) + y1 (dm/get-prop line-point1 :y) + x2 (dm/get-prop line-point2 :x) + y2 (dm/get-prop line-point2 :y)] + (/ (mth/abs (+ (* x0 (- y2 y1)) + (- (* y0 (- x2 x1))) + (* x2 y1) + (- (* y2 x1)))) + (distance line-point2 line-point1)))) -(defn almost-zero? [{:keys [x y] :as p}] - (assert (point? p)) - (and (mth/almost-zero? x) - (mth/almost-zero? y))) +(defn almost-zero? + [p] + (assert (point? p) "point instance expected") + (and ^boolean (mth/almost-zero? (dm/get-prop p :x)) + ^boolean (mth/almost-zero? (dm/get-prop p :y)))) (defn lerp "Calculates a linear interpolation between two points given a tvalue" [p1 p2 t] - (let [x (mth/lerp (:x p1) (:x p2) t) - y (mth/lerp (:y p1) (:y p2) t)] - (point x y))) + (let [x (mth/lerp (dm/get-prop p1 :x) (dm/get-prop p2 :x) t) + y (mth/lerp (dm/get-prop p1 :y) (dm/get-prop p2 :y) t)] + (Point. x y))) (defn rotate "Rotates the point around center with an angle" - [{px :x py :y} {cx :x cy :y} angle] + [p c angle] + (prn "ROTATE" p c angle) + (assert (point? p) "point instance expected") + (assert (point? c) "point instance expected") (let [angle (mth/radians angle) + px (dm/get-prop p :x) + py (dm/get-prop p :y) + cx (dm/get-prop c :x) + cy (dm/get-prop c :y) - x (+ (* (mth/cos angle) (- px cx)) - (* (mth/sin angle) (- py cy) -1) - cx) - - y (+ (* (mth/sin angle) (- px cx)) - (* (mth/cos angle) (- py cy)) - cy)] - (point x y))) + sa (mth/sin angle) + ca (mth/cos angle) + x (+ (* ca (- px cx)) + (* sa (- py cy) -1) + cx) + y (+ (* sa (- px cx)) + (* ca (- py cy)) + cy)] + (Point. x y))) (defn scale-from "Moves a point in the vector that creates with center with a scale @@ -331,10 +411,11 @@ (defn no-zeros "Remove zero values from either coordinate" - [point] - (-> point - (update :x #(if (mth/almost-zero? %) 0.001 %)) - (update :y #(if (mth/almost-zero? %) 0.001 %)))) + [p] + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y)] + (Point. (if (mth/almost-zero? x) 0.001 x) + (if (mth/almost-zero? y) 0.001 y)))) (defn abs diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 7b44280641..f6451ab022 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -41,8 +41,8 @@ corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] - (-> modifiers - (ctm/move deltav)))) + + (ctm/move modifiers deltav))) (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 057687e1e1..ca400800e2 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -7,6 +7,7 @@ (ns app.common.geom.shapes.rect (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.math :as mth])) @@ -90,8 +91,8 @@ maxy ##-Inf pts points] (if-let [pt (first pts)] - (let [x (d/get-prop pt :x) - y (d/get-prop pt :y)] + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] (recur (min minx x) (min miny y) (max maxx x) diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc new file mode 100644 index 0000000000..b83d70e7c7 --- /dev/null +++ b/common/test/common_tests/geom_point_test.cljc @@ -0,0 +1,295 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.geom-point-test + (:require + [app.common.geom.point :as gpt] + [clojure.test :as t])) + +(t/deftest add-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/add p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 5 (:y rs))))) + +(t/deftest substract-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/subtract p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= -1 (:x rs))) + (t/is (= -1 (:y rs))))) + +(t/deftest multiply-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/multiply p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 2 (:x rs))) + (t/is (= 6 (:y rs))))) + +(t/deftest divide-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5) + rs (gpt/divide p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 0.5 (:x rs))) + (t/is (= 0.4 (:y rs))))) + +(t/deftest min-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5)] + + (let [rs (gpt/min)] + (t/is (nil? rs))) + + (let [rs (gpt/min p1)] + (t/is (= rs p1))) + + (let [rs (gpt/min nil p1)] + (t/is (= rs p1))) + + (let [rs (gpt/min p1 nil)] + (t/is (= rs p1))) + + (let [rs (gpt/min p1 p2)] + (t/is (= rs p1))) + + (let [rs (gpt/min p2 p1)] + (t/is (= rs p1))) + )) + +(t/deftest max-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5)] + + (let [rs (gpt/max)] + (t/is (nil? rs))) + + (let [rs (gpt/max p1)] + (t/is (= rs p1))) + + (let [rs (gpt/max nil p1)] + (t/is (= rs p1))) + + (let [rs (gpt/max p1 nil)] + (t/is (= rs p1))) + + (let [rs (gpt/max p1 p2)] + (t/is (= rs p2))) + + (let [rs (gpt/max p2 p1)] + (t/is (= rs p2))) + )) + +(t/deftest inverse-point + (let [p1 (gpt/point 1 2) + rs (gpt/inverse p1)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 0.5 (:y rs))))) + +(t/deftest negate-point + (let [p1 (gpt/point 1 2) + rs (gpt/negate p1)] + (t/is (gpt/point? rs)) + (t/is (= -1 (:x rs))) + (t/is (= -2 (:y rs))))) + +(t/deftest distance-between-two-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 4 6) + rs (gpt/distance p1 p2)] + (t/is (number? rs)) + (t/is (= 5 rs)))) + +(t/deftest distance-vector-between-two-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/distance-vector p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 1 (:y rs))))) + +(t/deftest point-length + (let [p1 (gpt/point 1 10) + rs (gpt/length p1)] + (t/is (number? rs)) + (t/is (= 10.04987562112089 rs)))) + +(t/deftest point-angle-1 + (let [p1 (gpt/point 1 3) + rs (gpt/angle p1)] + (t/is (number? rs)) + (t/is (= 71.56505117707799 rs)))) + +(t/deftest point-angle-2 + (let [p1 (gpt/point 1 3) + p2 (gpt/point 2 4) + rs (gpt/angle p1 p2)] + (t/is (number? rs)) + (t/is (= -135 rs)))) + +(t/deftest point-angle-with-other + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 5) + rs (gpt/angle-with-other p1 p2)] + (t/is (number? rs)) + (t/is (= 7.125016348901757 rs)))) + +(t/deftest point-angle-sign + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 5) + rs (gpt/angle-sign p1 p2)] + (t/is (number? rs)) + (t/is (= 1 rs))) + + (let [p1 (gpt/point -11 -3) + p2 (gpt/point 1 5) + rs (gpt/angle-sign p1 p2)] + (t/is (number? rs)) + (t/is (= -1 rs))) + ) + +(t/deftest update-angle + (let [p1 (gpt/point 1 3) + rs (gpt/update-angle p1 10)] + (t/is (gpt/point? rs)) + (t/is (= 3.1142355569111246 (:x rs))) + (t/is (= 0.5491237529650835 (:y rs))))) + + +(t/deftest point-quadrant + (let [p1 (gpt/point 1 3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 1 rs))) + + (let [p1 (gpt/point 1 -3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 4 rs))) + + (let [p1 (gpt/point -1 3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 2 rs))) + + (let [p1 (gpt/point -1 -3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 3 rs))) + ) + +(t/deftest round-point + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/round p1)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 3 (:y rs)))) + + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/round p1 2)] + (t/is (gpt/point? rs)) + (t/is (= 1.35 (:x rs))) + (t/is (= 3.35 (:y rs)))) + ) + +(t/deftest halft-round-point + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/half-round p1)] + (t/is (gpt/point? rs)) + (t/is (= 1.5 (:x rs))) + (t/is (= 3.5 (:y rs))))) + +(t/deftest transform-point + ;;todo + ) + +(t/deftest scale-point + (let [p1 (gpt/point 1.5 3) + rs (gpt/scale p1 2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 6 (:y rs))))) + +(t/deftest dot-point + (let [p1 (gpt/point 1.5 3) + p2 (gpt/point 2 6) + rs (gpt/dot p1 p2)] + (t/is (number? rs)) + (t/is (= 21 rs)))) + +(t/deftest unit-point + (let [p1 (gpt/point 2 3) + rs (gpt/unit p1)] + (t/is (gpt/point? rs)) + (t/is (= 0.5547001962252291 (:x rs))) + (t/is (= 0.8320502943378437 (:y rs))))) + +(t/deftest project-point + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 6) + rs (gpt/project p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 0.5135135135135135 (:x rs))) + (t/is (= 3.081081081081081 (:y rs))))) + +(t/deftest center-points + (let [points [(gpt/point 0.5 0.5) + (gpt/point -1 -2) + (gpt/point 20 65.2) + (gpt/point 12 -10)] + rs (gpt/center-points points)] + (t/is (= 7.875 (:x rs))) + (t/is (= 13.425 (:y rs))))) + +(t/deftest normal-left-point + (let [p1 (gpt/point 2 3) + rs (gpt/normal-left p1)] + (t/is (gpt/point? rs)) + (t/is (= -0.8320502943378437 (:x rs))) + (t/is (= 0.5547001962252291 (:y rs))))) + +(t/deftest normal-right-point + (let [p1 (gpt/point 2 3) + rs (gpt/normal-right p1)] + (t/is (gpt/point? rs)) + (t/is (= 0.8320502943378437 (:x rs))) + (t/is (= -0.5547001962252291 (:y rs))))) + +(t/deftest point-line-distance + (let [p1 (gpt/point 2 -3) + p2 (gpt/point -1 4) + p3 (gpt/point 5 6) + rs (gpt/point-line-distance p1 p2 p3)] + (t/is (number? rs)) + (t/is (= 7.58946638440411 rs)))) + +(t/deftest almost-zero-predicate + (let [p1 (gpt/point 0.000001 0.0000002) + p2 (gpt/point 0.001 -0.0003)] + (t/is (gpt/almost-zero? p1)) + (t/is (not (gpt/almost-zero? p2))))) + +(t/deftest lerp-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/lerp p1 p2 2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 4 (:y rs))))) + +(t/deftest rotate-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/rotate p1 p2 11)] + (t/is (gpt/point? rs)) + (t/is (= 1.2091818119288809 (:x rs))) + (t/is (= 1.8275638211757912 (:y rs))))) + diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index f9585b5bf0..b4984e6308 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -297,8 +297,8 @@ (mapv (fn [[value points]] [(- value pval) (->> points (mapv #(vector point %)))])))))] - {:x (query-coord point :x) - :y (query-coord point :y)})) + (gpt/point (query-coord point :x) + (query-coord point :y)))) (defn merge-matches ([] {:x nil :y nil}) diff --git a/frontend/test/frontend_tests/test_helpers_shapes.cljs b/frontend/test/frontend_tests/test_helpers_shapes.cljs new file mode 100644 index 0000000000..6b84b8dd42 --- /dev/null +++ b/frontend/test/frontend_tests/test_helpers_shapes.cljs @@ -0,0 +1,53 @@ +(ns frontend-tests.test-helpers-shapes + (:require + [app.common.colors :as clr] + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.pages.helpers :as cph] + [app.main.data.workspace.libraries :as dwl] + [app.test-helpers.events :as the] + [app.test-helpers.libraries :as thl] + [app.test-helpers.pages :as thp] + [beicon.core :as rx] + [cljs.pprint :refer [pprint]] + [cljs.test :as t :include-macros true] + [clojure.stacktrace :as stk] + [linked.core :as lks] + [potok.core :as ptk])) + +(t/use-fixtures :each + {:before thp/reset-idmap!}) + +(t/deftest test-create-page + (t/testing "create page" + (let [state (-> thp/initial-state + (thp/sample-page)) + page (thp/current-page state)] + (t/is (= (:name page) "page1"))))) + +(t/deftest test-create-shape + (t/testing "create shape" + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"})) + shape (thp/get-shape state :shape1)] + (t/is (= (:name shape) "Rect 1"))))) + +(t/deftest asynctest + (t/testing "asynctest" + (t/async done + (let [state {} + color {:color clr/white} + + store (the/prepare-store state done + (fn [new-state] + (t/is (= (get-in new-state [:workspace-data + :recent-colors]) + [color]))))] + + (ptk/emit! + store + (dwl/add-recent-color color) + :the/end))))) + From 04243be4a54505e0cc4701c1cd6d66d582ad2be8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 20 Nov 2022 20:06:34 +0100 Subject: [PATCH 07/10] :paperclip: Update frontend bench namespace --- frontend/dev/bench.cljs | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs index cf2a406328..41171bafe8 100644 --- a/frontend/dev/bench.cljs +++ b/frontend/dev/bench.cljs @@ -1,6 +1,7 @@ (ns bench (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gsr] [app.common.perf :as perf] @@ -10,39 +11,21 @@ (def points (gen/sample (s/gen ::gpt/point) 20)) -(defn points->rect - [points] - (when-let [points (seq points)] - (loop [minx ##Inf - miny ##Inf - maxx ##-Inf - maxy ##-Inf - pts points] - (if-let [pt ^boolean (first pts)] - (let [x (d/get-prop pt :x) - y (d/get-prop pt :y)] - (recur (min minx x) - (min miny y) - (max maxx x) - (max maxy y) - (rest pts))) - (when (d/num? minx miny maxx maxy) - (gsr/make-rect minx miny (- maxx minx) (- maxy miny))))))) - (defn bench-points [] + #_(perf/benchmark + :f #(gpt/center-points-old points) + :samples 20 + :max-iterations 500000 + :name "base") (perf/benchmark - :f #(gsr/points->rect points) - :name "base") - (perf/benchmark - :f #(points->rect points) + :f #(gpt/center-points points) + :max-iterations 500000 + :samples 20 :name "optimized")) - (defn main [& [name]] (case name "points" (bench-points) (println "available: points"))) - - From 600f9ef07136e0adac7b1aabb922440f5487a8f2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 28 Nov 2022 13:05:54 +0100 Subject: [PATCH 08/10] :sparkles: Performance improvements --- common/src/app/common/types/modifiers.cljc | 103 ++++++++++++------ .../common_tests/types_modifiers_test.cljc | 26 +++++ 2 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 common/test/common_tests/types_modifiers_test.cljc diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index cd4b445b58..608d78ab10 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -7,6 +7,7 @@ (ns app.common.types.modifiers (:refer-clojure :exclude [empty empty?]) (:require + [app.common.perf :as perf] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -217,15 +218,44 @@ :property property :value value}))) +(defn- merge-geometry + [geometry other] + + (cond + (c/empty? geometry) + other + + (c/empty? other) + geometry + + :else + (loop [result geometry + modifiers (seq other)] + (if (c/empty? modifiers) + result + (let [current (first modifiers) + result + (cond + (= :move (:type current)) + (maybe-add-move result current) + + (= :resize (:type current)) + (maybe-add-resize result current) + + :else + (conj result current))] + + (recur result (rest modifiers))))))) + (defn add-modifiers [modifiers new-modifiers] (cond-> modifiers (some? (:geometry-child new-modifiers)) - (update :geometry-child #(d/concat-vec [] % (:geometry-child new-modifiers))) + (update :geometry-child merge-geometry (:geometry-child new-modifiers)) (some? (:geometry-parent new-modifiers)) - (update :geometry-parent #(d/concat-vec [] % (:geometry-parent new-modifiers))) + (update :geometry-parent merge-geometry (:geometry-parent new-modifiers)) (some? (:structure-parent new-modifiers)) (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) @@ -426,39 +456,46 @@ (defn modifiers->transform "Given a set of modifiers returns its transformation matrix" [modifiers] - (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}] - (case type - :move - (gmt/multiply (gmt/translate-matrix vector) matrix) - :resize - (let [origin (cond-> origin - (or (some? transform-inverse)(some? transform)) - (gpt/transform transform-inverse))] - (gmt/multiply - (-> (gmt/matrix) - (cond-> (some? transform) - (gmt/multiply transform)) - (gmt/translate origin) - (gmt/scale vector) - (gmt/translate (gpt/negate origin)) - (cond-> (some? transform-inverse) - (gmt/multiply transform-inverse))) - matrix)) + (let [modifiers + (if (d/not-empty? (:geometry-parent modifiers)) + (concat (:geometry-parent modifiers) (:geometry-child modifiers)) + (:geometry-child modifiers))] - :rotation - (gmt/multiply - (-> (gmt/matrix) - (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rotation)) - (gmt/translate (gpt/negate center))) - matrix)))] - (let [modifiers (if (d/not-empty? (:geometry-parent modifiers)) - (d/concat-vec (:geometry-parent modifiers) (:geometry-child modifiers)) - (:geometry-child modifiers))] - (when (d/not-empty? modifiers) - (->> modifiers - (reduce apply-modifier (gmt/matrix))))))) + (when (d/not-empty? modifiers) + (loop [matrix (gmt/matrix) + modifiers (seq modifiers)] + (if (c/empty? modifiers) + matrix + (let [{:keys [type vector rotation center origin transform transform-inverse] :as modifier} (first modifiers) + matrix + (case type + :move + (gmt/multiply (gmt/translate-matrix vector) matrix) + + :resize + (let [origin (cond-> origin + (or (some? transform-inverse)(some? transform)) + (gpt/transform transform-inverse))] + (gmt/multiply + (-> (gmt/matrix) + (cond-> (some? transform) + (gmt/multiply transform)) + (gmt/translate origin) + (gmt/scale vector) + (gmt/translate (gpt/negate origin)) + (cond-> (some? transform-inverse) + (gmt/multiply transform-inverse))) + matrix)) + + :rotation + (gmt/multiply + (-> (gmt/matrix) + (gmt/translate center) + (gmt/multiply (gmt/rotate-matrix rotation)) + (gmt/translate (gpt/negate center))) + matrix))] + (recur matrix (rest modifiers)))))))) (defn apply-structure-modifiers "Apply structure changes to a shape" diff --git a/common/test/common_tests/types_modifiers_test.cljc b/common/test/common_tests/types_modifiers_test.cljc new file mode 100644 index 0000000000..24d6e47a9a --- /dev/null +++ b/common/test/common_tests/types_modifiers_test.cljc @@ -0,0 +1,26 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.types-modifiers-test + (:require + [clojure.test :as t] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.types.modifiers :as ctm])) + +(t/deftest test-modifiers->transform + (let [modifiers + (-> (ctm/empty) + (ctm/move (gpt/point 100 200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/move (gpt/point -100 -200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/rotation (gpt/point 0 0) -100) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))) + + transform (ctm/modifiers->transform modifiers)] + + (t/is (not (gmt/close? (gmt/matrix) transform))))) From c79d549f539e70c9240fc366fb2e1b9d2880f2d1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 28 Nov 2022 18:20:35 +0100 Subject: [PATCH 09/10] :sparkles: Change modifiers to records --- common/src/app/common/geom/matrix.cljc | 69 ++- common/src/app/common/geom/point.cljc | 5 +- .../app/common/geom/shapes/constraints.cljc | 4 +- .../geom/shapes/flex_layout/modifiers.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 2 +- .../app/common/geom/shapes/transforms.cljc | 6 +- common/src/app/common/types/modifiers.cljc | 428 ++++++++++-------- common/test/common_tests/geom_point_test.cljc | 123 ++--- .../test/common_tests/geom_shapes_test.cljc | 2 +- frontend/dev/bench.cljs | 36 +- .../shapes/text/viewport_texts_html.cljs | 27 +- 11 files changed, 436 insertions(+), 268 deletions(-) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index ac7443f2d5..0158fe7df2 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -9,6 +9,7 @@ #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.math :as mth] [app.common.spec :as us] @@ -123,6 +124,35 @@ ([m1 m2 & others] (reduce multiply (multiply m1 m2) others))) +(defn multiply! + [^Matrix m1 ^Matrix m2] + (let [m1a (.-a m1) + m1b (.-b m1) + m1c (.-c m1) + m1d (.-d m1) + m1e (.-e m1) + m1f (.-f m1) + m2a (.-a m2) + m2b (.-b m2) + m2c (.-c m2) + m2d (.-d m2) + m2e (.-e m2) + m2f (.-f m2)] + #?@(:cljs [(set! (.-a m1) (+ (* m1a m2a) (* m1c m2b))) + (set! (.-b m1) (+ (* m1b m2a) (* m1d m2b))) + (set! (.-c m1) (+ (* m1a m2c) (* m1c m2d))) + (set! (.-d m1) (+ (* m1b m2c) (* m1d m2d))) + (set! (.-e m1) (+ (* m1a m2e) (* m1c m2f) m1e)) + (set! (.-f m1) (+ (* m1b m2e) (* m1d m2f) m1f)) + m1] + :clj [(Matrix. + (+ (* m1a m2a) (* m1c m2b)) + (+ (* m1b m2a) (* m1d m2b)) + (+ (* m1a m2c) (* m1c m2d)) + (+ (* m1b m2c) (* m1d m2d)) + (+ (* m1a m2e) (* m1c m2f) m1e) + (+ (* m1b m2e) (* m1d m2f) m1f))]))) + (defn add-translate "Given two TRANSLATE matrixes (only e and f have significative values), combine them. Quicker than multiplying them, for this @@ -147,26 +177,31 @@ (= v base)) (defn translate-matrix - ([{x :x y :y :as pt}] + ([pt] (assert (gpt/point? pt)) - (Matrix. 1 0 0 1 x y)) + (Matrix. 1 0 0 1 + (dm/get-prop pt :x) + (dm/get-prop pt :y))) ([x y] - (translate-matrix (gpt/point x y)))) + (Matrix. 1 0 0 1 x y))) (defn scale-matrix ([pt center] - (multiply (translate-matrix center) - (scale-matrix pt) - (translate-matrix (gpt/negate center)))) - ([{x :x y :y :as pt}] + (-> (matrix) + (multiply! (translate-matrix center)) + (multiply! (scale-matrix pt)) + (multiply! (translate-matrix (gpt/negate center))))) + ([pt] (assert (gpt/point? pt)) - (Matrix. x 0 0 y 0 0))) + (Matrix. (dm/get-prop pt :x) 0 0 (dm/get-prop pt :y) 0 0))) (defn rotate-matrix - ([angle point] (multiply (translate-matrix point) - (rotate-matrix angle) - (translate-matrix (gpt/negate point)))) + ([angle point] + (-> (matrix) + (multiply! (translate-matrix point)) + (multiply! (rotate-matrix angle)) + (multiply! (translate-matrix (gpt/negate point))))) ([angle] (let [a (mth/radians angle)] (Matrix. (mth/cos a) @@ -200,11 +235,23 @@ ([m scale center] (multiply m (scale-matrix scale center)))) +(defn scale! + "Apply scale transformation to the matrix." + ([m scale] + (multiply! m (scale-matrix scale))) + ([m scale center] + (multiply! m (scale-matrix scale center)))) + (defn translate "Apply translate transformation to the matrix." [m pt] (multiply m (translate-matrix pt))) +(defn translate! + "Apply translate transformation to the matrix." + [m pt] + (multiply! m (translate-matrix pt))) + (defn skew "Apply translate transformation to the matrix." ([m angle-x angle-y] diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 5421af3cef..e15f0fff0b 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -323,8 +323,9 @@ (/ (dm/get-prop p1 :y) p-length)))) (defn perpendicular - [{:keys [x y]}] - (Point. (- y) x)) + [pt] + (Point. (- (dm/get-prop pt :y)) + (dm/get-prop pt :x))) (defn project "V1 perpendicular projection on vector V2" diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 35ddd66a83..6275e673fb 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -282,7 +282,7 @@ (defn calc-child-modifiers [parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds] - (let [modifiers (ctm/select-child-modifiers modifiers) + (let [modifiers (ctm/select-child modifiers) constraints-h (if-not ignore-constraints @@ -299,7 +299,7 @@ (let [transformed-parent-bounds @transformed-parent-bounds - modifiers (ctm/select-child-modifiers modifiers) + modifiers (ctm/select-child modifiers) transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) modifiers (normalize-modifiers constraints-h constraints-v modifiers parent diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 8ad9364783..481d27390f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -34,7 +34,7 @@ resize-origin (gpo/origin transformed-child-bounds)] (-> modifiers - (ctm/select-child-modifiers) + (ctm/select-child) (ctm/resize resize-vector resize-origin diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index d0588cfa96..0677a42931 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -107,7 +107,7 @@ ;; Check the constraints, then resize (let [parent-id (:id parent) - parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent-modifiers modifiers))] + parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent modifiers))] (loop [modif-tree modif-tree children (seq children)] (if (empty? children) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 38a4ee7e6d..4383b28e88 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -483,9 +483,9 @@ ([points center modifiers] (let [transform (ctm/modifiers->transform modifiers)] - (cond-> points - (some? transform) - (gco/transform-points center transform))))) + (cond-> points + (some? transform) + (gco/transform-points center transform))))) (defn transform-selrect [selrect modifiers] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 608d78ab10..b0c8827008 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -7,7 +7,6 @@ (ns app.common.types.modifiers (:refer-clojure :exclude [empty empty?]) (:require - [app.common.perf :as perf] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -40,44 +39,123 @@ ;; * rotation ;; * change-properties +(defrecord Modifiers + [geometry-parent + geometry-child + structure-parent + structure-child]) + +(defrecord GeometricOperation + [type + vector + origin + transform + transform-inverse + rotation + center]) + +(defrecord StructureOperation + [type + property + value + index]) + +;; Record constructors + +(defn move-op + [vector] + (GeometricOperation. :move vector nil nil nil nil nil)) + +(defn resize-op + ([vector origin] + (GeometricOperation. :resize vector origin nil nil nil nil)) + ([vector origin transform transform-inverse] + (GeometricOperation. :resize vector origin transform transform-inverse nil nil))) + +(defn rotation-geom-op + [center angle] + (GeometricOperation. :rotation nil nil nil nil angle center)) + +(defn rotation-struct-op + [angle] + (StructureOperation. :rotation nil angle nil)) + +(defn remove-children-op + [shapes] + (StructureOperation. :remove-children nil shapes nil)) + +(defn add-children-op + [shapes index] + (StructureOperation. :add-children nil shapes index)) + +(defn reflow-op + [] + (StructureOperation. :reflow nil nil nil)) + +(defn scale-content-op + [value] + (StructureOperation. :scale-content nil value nil)) + +(defn change-property-op + [property value] + (StructureOperation. :change-property property value nil)) + + ;; Private aux functions -(def conjv (fnil conj [])) +(defn- move-vec? + [vector] + (or (not (mth/almost-zero? (dm/get-prop vector :x))) + (not (mth/almost-zero? (dm/get-prop vector :y))))) -(defn- move-vec? [vector] - (or (not (mth/almost-zero? (:x vector))) - (not (mth/almost-zero? (:y vector))))) - -(defn- resize-vec? [vector] - (or (not (mth/almost-zero? (- (:x vector) 1))) - (not (mth/almost-zero? (- (:y vector) 1))))) +(defn- resize-vec? + [vector] + (or (not (mth/almost-zero? (- (dm/get-prop vector :x) 1))) + (not (mth/almost-zero? (- (dm/get-prop vector :y) 1))))) (defn- mergeable-move? [op1 op2] - (and (= :move (:type op1)) - (= :move (:type op2)))) + (let [type-op1 (dm/get-prop op1 :type) + type-op2 (dm/get-prop op2 :type)] + (and (= :move type-op1) (= :move type-op2)))) (defn- mergeable-resize? [op1 op2] - (and (= :resize (:type op1)) - (= :resize (:type op2)) + (let [type-op1 (dm/get-prop op1 :type) + transform-op1 (or (dm/get-prop op1 :transform) (gmt/matrix)) + transform-inv-op1 (or (dm/get-prop op1 :transform-inverse) (gmt/matrix)) + origin-op1 (dm/get-prop op1 :origin) - ;; Same transforms - (gmt/close? (or (:transform op1) (gmt/matrix)) (or (:transform op2) (gmt/matrix))) - (gmt/close? (or (:transform-inverse op1) (gmt/matrix)) (or (:transform-inverse op2) (gmt/matrix))) + type-op2 (dm/get-prop op2 :type) + transform-op2 (or (dm/get-prop op2 :transform) (gmt/matrix)) + transform-inv-op2 (or (dm/get-prop op2 :transform-inverse) (gmt/matrix)) + origin-op2 (dm/get-prop op2 :origin)] + (and (= :resize type-op1) (= :resize type-op2) - ;; Same origin - (gpt/close? (:origin op1) (:origin op2)))) + ;; Same origin + (gpt/close? origin-op1 origin-op2) + + ;; Same transforms + (gmt/close? transform-op1 transform-op2) + (gmt/close? transform-inv-op1 transform-inv-op2)))) (defn- merge-move [op1 op2] - {:type :move - :vector (gpt/add (:vector op1) (:vector op2))}) + (let [vector-op1 (dm/get-prop op1 :vector) + vector-op2 (dm/get-prop op2 :vector)] + (move-op (gpt/add vector-op1 vector-op2)))) (defn- merge-resize [op1 op2] - (let [vector (gpt/point (* (-> op1 :vector :x) (-> op2 :vector :x)) - (* (-> op1 :vector :y) (-> op2 :vector :y)))] + (let [op1-vector (dm/get-prop op1 :vector) + op1-x (dm/get-prop op1-vector :x) + op1-y (dm/get-prop op1-vector :y) + + op2-vector (dm/get-prop op2 :vector) + op2-x (dm/get-prop op2-vector :x) + op2-y (dm/get-prop op2-vector :y) + + vector (gpt/point (* op1-x op2-x) (* op1-y op2-y))] (assoc op1 :vector vector))) (defn- maybe-add-move @@ -89,7 +167,7 @@ (if (mergeable-move? head op) (let [item (merge-move head op)] (cond-> (pop operations) - (move-vec? (:vector item)) + (move-vec? (dm/get-prop item :vector)) (conj item))) (conj operations op))))) @@ -103,21 +181,23 @@ (if (mergeable-resize? head op) (let [item (merge-resize head op)] (cond-> (pop operations) - (resize-vec? (:vector item)) + (resize-vec? (dm/get-prop item :vector)) (conj item))) (conj operations op))))) (defn valid-vector? - [{:keys [x y]}] - (and (some? x) - (some? y) - (not (mth/nan? x)) - (not (mth/nan? y)))) + [vector] + (let [x (dm/get-prop vector :x) + y (dm/get-prop vector :y)] + (and (some? x) + (some? y) + (not (mth/nan? x)) + (not (mth/nan? y))))) ;; Public builder API (defn empty [] - {}) + (Modifiers. [] [] [] [])) (defn move-parent ([modifiers x y] @@ -125,143 +205,118 @@ ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (move-vec? vector) - (update :geometry-parent maybe-add-move {:type :move :vector vector})))) + (update :geometry-parent maybe-add-move (move-op vector))))) (defn resize-parent ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-parent maybe-add-resize {:type :resize - :vector vector - :origin origin}))) + (update :geometry-parent maybe-add-resize (resize-op vector origin)))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-parent maybe-add-resize {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-parent maybe-add-resize (resize-op vector origin transform transform-inverse))))) + (defn move ([modifiers x y] (move modifiers (gpt/point x y))) ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (move-vec? vector) - (update :geometry-child maybe-add-move {:type :move :vector vector})))) + (update :geometry-child maybe-add-move (move-op vector))))) (defn resize ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-child maybe-add-resize {:type :resize - :vector vector - :origin origin}))) + (update :geometry-child maybe-add-resize (resize-op vector origin)))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-child maybe-add-resize {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-child maybe-add-resize (resize-op vector origin transform transform-inverse))))) (defn rotation [modifiers center angle] - (cond-> modifiers + (cond-> (or modifiers (empty)) (not (mth/close? angle 0)) - (-> (update :structure-child conjv {:type :rotation - :rotation angle}) - (update :geometry-child conjv {:type :rotation - :center center - :rotation angle})))) + (-> (update :structure-child conj (rotation-struct-op angle)) + (update :geometry-child conj (rotation-geom-op center angle))))) (defn remove-children [modifiers shapes] - (cond-> modifiers + (cond-> (or modifiers (empty)) (d/not-empty? shapes) - (update :structure-parent conjv {:type :remove-children - :value shapes}))) + (update :structure-parent conj (remove-children-op shapes)))) (defn add-children [modifiers shapes index] - (cond-> modifiers + (cond-> (or modifiers (empty)) (d/not-empty? shapes) - (update :structure-parent conjv {:type :add-children - :value shapes - :index index}))) + (update :structure-parent conj (add-children-op shapes index)))) (defn reflow [modifiers] - (-> modifiers - (update :structure-parent conjv {:type :reflow}))) + (-> (or modifiers (empty)) + (update :structure-parent conj (reflow-op)))) (defn scale-content [modifiers value] - (-> modifiers - (update :structure-child conjv {:type :scale-content :value value}))) + (-> (or modifiers (empty)) + (update :structure-child conj (scale-content-op value)))) (defn change-property [modifiers property value] - (-> modifiers - (update :structure-child conjv {:type :change-property - :property property - :value value}))) + (-> (or modifiers (empty)) + (update :structure-child conj (change-property-op property value)))) (defn- merge-geometry - [geometry other] + [operations other] (cond - (c/empty? geometry) + (c/empty? operations) other (c/empty? other) - geometry + operations :else - (loop [result geometry - modifiers (seq other)] - (if (c/empty? modifiers) + (loop [result operations + operations (seq other)] + (if (c/empty? operations) result - (let [current (first modifiers) + (let [current (first operations) result (cond - (= :move (:type current)) + (= :move (dm/get-prop current :type)) (maybe-add-move result current) - (= :resize (:type current)) + (= :resize (dm/get-prop current :type)) (maybe-add-resize result current) :else (conj result current))] - (recur result (rest modifiers))))))) + (recur result (rest operations))))))) (defn add-modifiers [modifiers new-modifiers] - - (cond-> modifiers - (some? (:geometry-child new-modifiers)) - (update :geometry-child merge-geometry (:geometry-child new-modifiers)) - - (some? (:geometry-parent new-modifiers)) - (update :geometry-parent merge-geometry (:geometry-parent new-modifiers)) - - (some? (:structure-parent new-modifiers)) - (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) - - (some? (:structure-child new-modifiers)) - (update :structure-child #(d/concat-vec [] % (:structure-child new-modifiers))))) + (let [modifiers (or modifiers (empty)) + new-modifiers (or new-modifiers (empty))] + (-> modifiers + (update :geometry-child merge-geometry (dm/get-prop new-modifiers :geometry-child)) + (update :geometry-parent merge-geometry (dm/get-prop new-modifiers :geometry-parent)) + (update :structure-parent #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-parent))) + (update :structure-child #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-child)))))) ;; These are convenience methods to create single operation modifiers without the builder @@ -385,27 +440,27 @@ (defn empty? [modifiers] - (and (c/empty? (:geometry-child modifiers)) - (c/empty? (:geometry-parent modifiers)) - (c/empty? (:structure-parent modifiers)) - (c/empty? (:structure-child modifiers)))) + (and (c/empty? (dm/get-prop modifiers :geometry-child)) + (c/empty? (dm/get-prop modifiers :geometry-parent)) + (c/empty? (dm/get-prop modifiers :structure-parent)) + (c/empty? (dm/get-prop modifiers :structure-child)))) (defn child-modifiers? - [{:keys [geometry-child structure-child]}] - (or (d/not-empty? geometry-child) - (d/not-empty? structure-child))) + [modifiers] + (or (d/not-empty? (dm/get-prop modifiers :geometry-child)) + (d/not-empty? (dm/get-prop modifiers :structure-child)))) (defn only-move? "Returns true if there are only move operations" - [{:keys [geometry-child geometry-parent]}] - (let [move-op? #(= :move (:type %))] - (and (every? move-op? geometry-child) - (every? move-op? geometry-parent)))) + [modifiers] + (let [move-op? #(= :move (dm/get-prop % :type))] + (and (every? move-op? (dm/get-prop modifiers :geometry-child)) + (every? move-op? (dm/get-prop modifiers :geometry-parent))))) (defn has-geometry? - [{:keys [geometry-parent geometry-child]}] - (or (d/not-empty? geometry-parent) - (d/not-empty? geometry-child))) + [modifiers] + (or (d/not-empty? (dm/get-prop modifiers :geometry-parent)) + (d/not-empty? (dm/get-prop modifiers :geometry-child)))) (defn has-structure? [{:keys [structure-parent structure-child]}] @@ -414,25 +469,25 @@ ;; Extract subsets of modifiers -(defn select-child-modifiers +(defn select-child [modifiers] - (select-keys modifiers [:geometry-child :structure-child])) + (assoc (or modifiers (empty)) :geometry-parent [] :structure-parent [])) -(defn select-child-geometry-modifiers +(defn select-parent [modifiers] - (select-keys modifiers [:geometry-child])) - -(defn select-parent-modifiers - [modifiers] - (select-keys modifiers [:geometry-parent :structure-parent])) + (assoc (or modifiers (empty)) :geometry-child [] :structure-child [])) (defn select-structure [modifiers] - (select-keys modifiers [:structure-parent :structure-child])) + (assoc (or modifiers (empty)) :geometry-child [] :geometry-parent [])) (defn select-geometry [modifiers] - (select-keys modifiers [:geometry-parent :geometry-child])) + (assoc (or modifiers (empty)) :structure-child [] :structure-parent [])) + +(defn select-child-geometry-modifiers + [modifiers] + (-> modifiers select-child select-geometry)) (defn added-children-frames "Returns the frames that have an 'add-children' operation" @@ -456,46 +511,53 @@ (defn modifiers->transform "Given a set of modifiers returns its transformation matrix" [modifiers] + (let [modifiers (concat (dm/get-prop modifiers :geometry-parent) + (dm/get-prop modifiers :geometry-child))] - (let [modifiers - (if (d/not-empty? (:geometry-parent modifiers)) - (concat (:geometry-parent modifiers) (:geometry-child modifiers)) - (:geometry-child modifiers))] + (loop [matrix (gmt/matrix) + modifiers (seq modifiers)] + (if (c/empty? modifiers) + matrix + (let [modifier (first modifiers) + type (dm/get-prop modifier :type) - (when (d/not-empty? modifiers) - (loop [matrix (gmt/matrix) - modifiers (seq modifiers)] - (if (c/empty? modifiers) - matrix - (let [{:keys [type vector rotation center origin transform transform-inverse] :as modifier} (first modifiers) - matrix - (case type - :move - (gmt/multiply (gmt/translate-matrix vector) matrix) + matrix + (case type + :move + (-> (dm/get-prop modifier :vector) + (gmt/translate-matrix) + (gmt/multiply! matrix)) - :resize - (let [origin (cond-> origin - (or (some? transform-inverse)(some? transform)) - (gpt/transform transform-inverse))] - (gmt/multiply - (-> (gmt/matrix) - (cond-> (some? transform) - (gmt/multiply transform)) - (gmt/translate origin) - (gmt/scale vector) - (gmt/translate (gpt/negate origin)) - (cond-> (some? transform-inverse) - (gmt/multiply transform-inverse))) - matrix)) + :resize + (let [tf (dm/get-prop modifier :transform) + tfi (dm/get-prop modifier :transform-inverse) + vector (dm/get-prop modifier :vector) + origin (dm/get-prop modifier :origin) + origin (if ^boolean (some? tfi) + (gpt/transform origin tfi) + origin)] - :rotation - (gmt/multiply + (gmt/multiply! (-> (gmt/matrix) - (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rotation)) - (gmt/translate (gpt/negate center))) - matrix))] - (recur matrix (rest modifiers)))))))) + (cond-> ^boolean (some? tf) + (gmt/multiply! tf)) + (gmt/translate! origin) + (gmt/scale! vector) + (gmt/translate! (gpt/negate origin)) + (cond-> ^boolean (some? tfi) + (gmt/multiply! tfi))) + matrix)) + + :rotation + (let [center (dm/get-prop modifier :center) + rotation (dm/get-prop modifier :rotation)] + (gmt/multiply! + (-> (gmt/matrix) + (gmt/translate! center) + (gmt/multiply! (gmt/rotate-matrix rotation)) + (gmt/translate! (gpt/negate center))) + matrix)))] + (recur matrix (next modifiers))))))) (defn apply-structure-modifiers "Apply structure changes to a shape" @@ -519,36 +581,48 @@ (cond-> shape (cph/text-shape? shape) (update :content scale-text-content value)))] + (let [remove-children (fn [shapes children-to-remove] (let [remove? (set children-to-remove)] (d/removev remove? shapes))) apply-modifier - (fn [shape {:keys [type property value index rotation]}] - (cond-> shape - (= type :rotation) - (update :rotation #(mod (+ % rotation) 360)) + (fn [shape operation] + (let [type (dm/get-prop operation :type)] + (case type + :rotation + (let [rotation (dm/get-prop operation :value)] + (update shape :rotation #(mod (+ (or % 0) rotation) 360))) - (and (= type :add-children) (some? index)) - (update :shapes - (fn [shapes] - (if (vector? shapes) - (cph/insert-at-index shapes index value) - (d/concat-vec shapes value)))) + :add-children + (let [value (dm/get-prop operation :value) + index (dm/get-prop operation :index)] + (if (some? index) + (update shape :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) + (update shape :shapes d/concat-vec value))) - (and (= type :add-children) (nil? index)) - (update :shapes d/concat-vec value) + :remove-children + (let [value (dm/get-prop operation :value)] + (update shape :shapes remove-children value)) - (= type :remove-children) - (update :shapes remove-children value) - (= type :scale-content) - (apply-scale-content value) + :scale-content + (let [value (dm/get-prop operation :value)] + (apply-scale-content shape value)) - (= type :change-property) - (assoc property value)))] + :change-property + (let [property (dm/get-prop operation :property) + value (dm/get-prop operation :value)] + (assoc shape property value)) + + ;; :default => no change to shape + shape)))] (as-> shape $ - (reduce apply-modifier $ (:structure-parent modifiers)) - (reduce apply-modifier $ (:structure-child modifiers)))))) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-child)))))) diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc index b83d70e7c7..c980523151 100644 --- a/common/test/common_tests/geom_point_test.cljc +++ b/common/test/common_tests/geom_point_test.cljc @@ -6,6 +6,7 @@ (ns common-tests.geom-point-test (:require + [app.common.math :as mth] [app.common.geom.point :as gpt] [clojure.test :as t])) @@ -14,32 +15,32 @@ p2 (gpt/point 2 3) rs (gpt/add p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 5 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 5 (:y rs))))) (t/deftest substract-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/subtract p1 p2)] (t/is (gpt/point? rs)) - (t/is (= -1 (:x rs))) - (t/is (= -1 (:y rs))))) + (t/is (mth/close? -1 (:x rs))) + (t/is (mth/close? -1 (:y rs))))) (t/deftest multiply-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/multiply p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 2 (:x rs))) - (t/is (= 6 (:y rs))))) + (t/is (mth/close? 2 (:x rs))) + (t/is (mth/close? 6 (:y rs))))) (t/deftest divide-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 5) rs (gpt/divide p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 0.5 (:x rs))) - (t/is (= 0.4 (:y rs))))) + (t/is (mth/close? 0.5 (:x rs))) + (t/is (mth/close? 0.4 (:y rs))))) (t/deftest min-point (let [p1 (gpt/point 1 2) @@ -49,19 +50,19 @@ (t/is (nil? rs))) (let [rs (gpt/min p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min nil p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p1 nil)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p1 p2)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p2 p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) )) (t/deftest max-point @@ -72,140 +73,140 @@ (t/is (nil? rs))) (let [rs (gpt/max p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max nil p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max p1 nil)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max p1 p2)] - (t/is (= rs p2))) + (t/is (gpt/close? rs p2))) (let [rs (gpt/max p2 p1)] - (t/is (= rs p2))) + (t/is (gpt/close? rs p2))) )) (t/deftest inverse-point (let [p1 (gpt/point 1 2) rs (gpt/inverse p1)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 0.5 (:y rs))))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 0.5 (:y rs))))) (t/deftest negate-point (let [p1 (gpt/point 1 2) rs (gpt/negate p1)] (t/is (gpt/point? rs)) - (t/is (= -1 (:x rs))) - (t/is (= -2 (:y rs))))) + (t/is (mth/close? -1 (:x rs))) + (t/is (mth/close? -2 (:y rs))))) (t/deftest distance-between-two-points (let [p1 (gpt/point 1 2) p2 (gpt/point 4 6) rs (gpt/distance p1 p2)] (t/is (number? rs)) - (t/is (= 5 rs)))) + (t/is (mth/close? 5 rs)))) (t/deftest distance-vector-between-two-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/distance-vector p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 1 (:y rs))))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 1 (:y rs))))) (t/deftest point-length (let [p1 (gpt/point 1 10) rs (gpt/length p1)] (t/is (number? rs)) - (t/is (= 10.04987562112089 rs)))) + (t/is (mth/close? 10.04987562112089 rs)))) (t/deftest point-angle-1 (let [p1 (gpt/point 1 3) rs (gpt/angle p1)] (t/is (number? rs)) - (t/is (= 71.56505117707799 rs)))) + (t/is (mth/close? 71.56505117707799 rs)))) (t/deftest point-angle-2 (let [p1 (gpt/point 1 3) p2 (gpt/point 2 4) rs (gpt/angle p1 p2)] (t/is (number? rs)) - (t/is (= -135 rs)))) + (t/is (mth/close? -135 rs)))) (t/deftest point-angle-with-other (let [p1 (gpt/point 1 3) p2 (gpt/point 1 5) rs (gpt/angle-with-other p1 p2)] (t/is (number? rs)) - (t/is (= 7.125016348901757 rs)))) + (t/is (mth/close? 7.125016348901757 rs)))) (t/deftest point-angle-sign (let [p1 (gpt/point 1 3) p2 (gpt/point 1 5) rs (gpt/angle-sign p1 p2)] (t/is (number? rs)) - (t/is (= 1 rs))) + (t/is (mth/close? 1 rs))) (let [p1 (gpt/point -11 -3) p2 (gpt/point 1 5) rs (gpt/angle-sign p1 p2)] (t/is (number? rs)) - (t/is (= -1 rs))) + (t/is (mth/close? -1 rs))) ) (t/deftest update-angle (let [p1 (gpt/point 1 3) rs (gpt/update-angle p1 10)] (t/is (gpt/point? rs)) - (t/is (= 3.1142355569111246 (:x rs))) - (t/is (= 0.5491237529650835 (:y rs))))) + (t/is (mth/close? 3.1142355569111246 (:x rs))) + (t/is (mth/close? 0.5491237529650835 (:y rs))))) (t/deftest point-quadrant (let [p1 (gpt/point 1 3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 1 rs))) + (t/is (mth/close? 1 rs))) (let [p1 (gpt/point 1 -3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 4 rs))) + (t/is (mth/close? 4 rs))) (let [p1 (gpt/point -1 3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 2 rs))) + (t/is (mth/close? 2 rs))) (let [p1 (gpt/point -1 -3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 3 rs))) + (t/is (mth/close? 3 rs))) ) (t/deftest round-point (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/round p1)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 3 (:y rs)))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 3 (:y rs)))) (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/round p1 2)] (t/is (gpt/point? rs)) - (t/is (= 1.35 (:x rs))) - (t/is (= 3.35 (:y rs)))) + (t/is (mth/close? 1.35 (:x rs))) + (t/is (mth/close? 3.35 (:y rs)))) ) (t/deftest halft-round-point (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/half-round p1)] (t/is (gpt/point? rs)) - (t/is (= 1.5 (:x rs))) - (t/is (= 3.5 (:y rs))))) + (t/is (mth/close? 1.5 (:x rs))) + (t/is (mth/close? 3.5 (:y rs))))) (t/deftest transform-point ;;todo @@ -215,30 +216,30 @@ (let [p1 (gpt/point 1.5 3) rs (gpt/scale p1 2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 6 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 6 (:y rs))))) (t/deftest dot-point (let [p1 (gpt/point 1.5 3) p2 (gpt/point 2 6) rs (gpt/dot p1 p2)] (t/is (number? rs)) - (t/is (= 21 rs)))) + (t/is (mth/close? 21 rs)))) (t/deftest unit-point (let [p1 (gpt/point 2 3) rs (gpt/unit p1)] (t/is (gpt/point? rs)) - (t/is (= 0.5547001962252291 (:x rs))) - (t/is (= 0.8320502943378437 (:y rs))))) + (t/is (mth/close? 0.5547001962252291 (:x rs))) + (t/is (mth/close? 0.8320502943378437 (:y rs))))) (t/deftest project-point (let [p1 (gpt/point 1 3) p2 (gpt/point 1 6) rs (gpt/project p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 0.5135135135135135 (:x rs))) - (t/is (= 3.081081081081081 (:y rs))))) + (t/is (mth/close? 0.5135135135135135 (:x rs))) + (t/is (mth/close? 3.081081081081081 (:y rs))))) (t/deftest center-points (let [points [(gpt/point 0.5 0.5) @@ -246,22 +247,22 @@ (gpt/point 20 65.2) (gpt/point 12 -10)] rs (gpt/center-points points)] - (t/is (= 7.875 (:x rs))) - (t/is (= 13.425 (:y rs))))) + (t/is (mth/close? 7.875 (:x rs))) + (t/is (mth/close? 13.425 (:y rs))))) (t/deftest normal-left-point (let [p1 (gpt/point 2 3) rs (gpt/normal-left p1)] (t/is (gpt/point? rs)) - (t/is (= -0.8320502943378437 (:x rs))) - (t/is (= 0.5547001962252291 (:y rs))))) + (t/is (mth/close? -0.8320502943378437 (:x rs))) + (t/is (mth/close? 0.5547001962252291 (:y rs))))) (t/deftest normal-right-point (let [p1 (gpt/point 2 3) rs (gpt/normal-right p1)] (t/is (gpt/point? rs)) - (t/is (= 0.8320502943378437 (:x rs))) - (t/is (= -0.5547001962252291 (:y rs))))) + (t/is (mth/close? 0.8320502943378437 (:x rs))) + (t/is (mth/close? -0.5547001962252291 (:y rs))))) (t/deftest point-line-distance (let [p1 (gpt/point 2 -3) @@ -269,7 +270,7 @@ p3 (gpt/point 5 6) rs (gpt/point-line-distance p1 p2 p3)] (t/is (number? rs)) - (t/is (= 7.58946638440411 rs)))) + (t/is (mth/close? 7.58946638440411 rs)))) (t/deftest almost-zero-predicate (let [p1 (gpt/point 0.000001 0.0000002) @@ -282,14 +283,14 @@ p2 (gpt/point 2 3) rs (gpt/lerp p1 p2 2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 4 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 4 (:y rs))))) (t/deftest rotate-point (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/rotate p1 p2 11)] (t/is (gpt/point? rs)) - (t/is (= 1.2091818119288809 (:x rs))) - (t/is (= 1.8275638211757912 (:y rs))))) + (t/is (mth/close? 1.2091818119288809 (:x rs))) + (t/is (mth/close? 1.8275638211757912 (:y rs))))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index a9d1e2df74..07f971e6f4 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -140,7 +140,7 @@ (t/testing "Transform shape with rotation modifiers" (t/are [type] (let [shape-before (create-test-shape type) - modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30 ) + modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30) shape-before (assoc shape-before :modifiers modifiers) shape-after (gsh/transform-shape shape-before)] diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs index 41171bafe8..bc1b6cdf81 100644 --- a/frontend/dev/bench.cljs +++ b/frontend/dev/bench.cljs @@ -3,8 +3,10 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] + [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gsr] [app.common.perf :as perf] + [app.common.types.modifiers :as ctm] [clojure.spec.alpha :as s] [clojure.test.check.generators :as gen])) @@ -24,8 +26,40 @@ :samples 20 :name "optimized")) +(def modifiers + (-> (ctm/empty) + (ctm/move (gpt/point 100 200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/move (gpt/point -100 -200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/rotation (gpt/point 0 0) -100) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)))) + +(defn bench-modifiers + [] + (perf/benchmark + :f #(ctm/modifiers->transform modifiers) + :max-iterations 50000 + :samples 20 + :name "current") + + #_(perf/benchmark + :f #(ctm/modifiers->transform-2 modifiers) + :max-iterations 50000 + :samples 20 + :name "optimized")) + +;; (ctm/modifiers->transform-2 modifiers) + +(defn ^:dev/after-load after-load + [] + #_(bench-modifiers)) + (defn main [& [name]] (case name "points" (bench-points) - (println "available: points"))) + "modifiers" (bench-modifiers) + (println "available: points")) + #_(.exit js/process 0)) + diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 6798a4a5e1..673f47a35e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -47,10 +47,8 @@ (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (dm/get-in modifiers [id :modifiers])] (-> shape - (cond-> (and (some? modifier) - (not (ctm/only-move? modifier))) + (cond-> (and (some? modifier) (not (ctm/only-move? modifier))) (fix-position modifier)) - (cond-> (nil? (:position-data shape)) (assoc :migrate true)) strip-position-data))) @@ -132,6 +130,21 @@ :shape shape :grow-type (:grow-type shape)}])) +(defn text-properties-equal? + [shape other] + (or (identical? shape other) + (and + ;; Check if both shapes are equivalent removing their geometry data + (= (dissoc shape :migrate :points :selrect :height :width :x :y) + (dissoc other :migrate :points :selrect :height :width :x :y)) + + ;; Check if the position and size is close. If any of these changes the shape has changed + ;; and if not there is no geometry relevant change + (mth/close? (:x shape) (:x other)) + (mth/close? (:y shape) (:y other)) + (mth/close? (:width shape) (:width other)) + (mth/close? (:height shape) (:height other))))) + (mf/defc viewport-texts-wrapper {::mf/wrap-props false ::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} @@ -149,12 +162,9 @@ old-modifiers (ctm/select-geometry (get prev-modifiers id)) new-modifiers (ctm/select-geometry (get modifiers id)) - remote? (some? (-> new-shape meta :session-id)) ] - + remote? (some? (-> new-shape meta :session-id))] (or (and (not remote?) - (not (identical? old-shape new-shape)) - (not= (dissoc old-shape :migrate) - (dissoc new-shape :migrate))) + (not (text-properties-equal? old-shape new-shape))) (and (not= new-modifiers old-modifiers) (or (not (ctm/only-move? new-modifiers)) @@ -172,6 +182,7 @@ handle-update-modifier (mf/use-callback update-text-modifier) handle-update-shape (mf/use-callback update-text-shape)] + [:* (for [{:keys [id] :as shape} changed-texts] [:& text-container {:shape shape From 0d2b228eb779aaaff84e699e3c162cafe641d905 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 30 Nov 2022 09:55:21 +0100 Subject: [PATCH 10/10] :sparkles: Keep group constraint behaviour inside flex layout --- .../src/app/common/geom/shapes/modifiers.cljc | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 0677a42931..bedcb1a1de 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -236,7 +236,24 @@ (and (some? auto-height) (ctl/auto-height? parent)) (set-parent-auto-height auto-height)))) -(defn- propagate-modifiers +(defn- propagate-modifiers-constraints + "Propagate modifiers to its children" + [objects bounds ignore-constraints modif-tree parent] + (let [parent-id (:id parent) + root? (= uuid/zero parent-id) + modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) + (ctm/select-geometry)) + has-modifiers? (ctm/child-modifiers? modifiers) + layout? (ctl/layout? parent) + parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) + + transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] + + (cond-> modif-tree + (and (not layout?) has-modifiers? parent? (not root?)) + (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints)))) + +(defn- propagate-modifiers-layout "Propagate modifiers to its children" [objects bounds ignore-constraints [modif-tree autolayouts] parent] (let [parent-id (:id parent) @@ -248,14 +265,11 @@ auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) - ;; If the current child is inside the layout we ignore the constraints - inside-layout? (ctl/inside-layout? objects parent) - transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] [(cond-> modif-tree (and (not layout?) has-modifiers? parent? (not root?)) - (set-children-modifiers objects bounds parent transformed-parent-bounds (or ignore-constraints inside-layout?)) + (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints) layout? (-> (process-layout-children objects bounds parent transformed-parent-bounds) @@ -280,18 +294,19 @@ other-tree)) (defn transform-bounds - [bounds objects modif-tree] + ([bounds objects modif-tree] + (transform-bounds bounds objects modif-tree (->> (keys modif-tree) (map #(get objects %))))) + ([bounds objects modif-tree tree-seq] - (loop [result bounds - ids (keys modif-tree)] - (if (empty? ids) - result + (loop [result bounds + shapes (reverse tree-seq)] + (if (empty? shapes) + result - (let [id (first ids) - shape (get objects id) - new-bounds (delay (get-group-bounds objects bounds modif-tree shape)) - result (assoc result id new-bounds)] - (recur result (rest ids)))))) + (let [shape (first shapes) + new-bounds (delay (get-group-bounds objects bounds modif-tree shape)) + result (assoc result (:id shape) new-bounds)] + (recur result (rest shapes))))))) (defn sizing-auto-modifiers "Recalculates the layouts to adjust the sizing: auto new sizes" @@ -308,7 +323,7 @@ tree-seq (resolve-tree-sequence #{current} objects) [resize-modif-tree _] - (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) + (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) bounds (transform-bounds bounds objects resize-modif-tree) @@ -320,12 +335,18 @@ [modif-tree objects ignore-constraints snap-pixel?] (let [objects (apply-structure-modifiers objects modif-tree) - bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) + bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) - [modif-tree sizing-auto-layouts] - (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [modif-tree #{}] shapes-tree) + ;; Calculate the input transformation and constraints + modif-tree' (reduce #(propagate-modifiers-constraints objects bounds ignore-constraints %1 %2) modif-tree shapes-tree) + bounds (transform-bounds bounds objects modif-tree' shapes-tree) + + [modif-tree-layout sizing-auto-layouts] + (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}] shapes-tree) + + modif-tree (merge-modif-tree modif-tree' modif-tree-layout) ;; Calculate hug layouts positions modif-tree