From 72d92c419f6e75f9d1e8f821fd3dda460a618b95 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 15 Jan 2020 17:59:55 +0100 Subject: [PATCH] :construction: More work on rotation related stuff. --- frontend/src/uxbox/main/data/projects.cljs | 2 +- frontend/src/uxbox/main/data/workspace.cljs | 69 ++--- frontend/src/uxbox/main/geom.cljs | 174 +++++------- frontend/src/uxbox/main/ui/shapes/rect.cljs | 21 +- .../src/uxbox/main/ui/workspace/drawarea.cljs | 4 +- .../uxbox/main/ui/workspace/selection.cljs | 101 +++---- frontend/src/uxbox/util/geom/matrix.cljs | 253 +++++++++++------- frontend/src/uxbox/util/geom/point.cljs | 7 +- frontend/tools.clj | 1 + 9 files changed, 333 insertions(+), 299 deletions(-) diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index ce3d04c60..8f99bade0 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -354,7 +354,7 @@ (ptk/reify ::create-empty-page ptk/WatchEvent (watch [this state stream] - (let [file-id (get-in state [:workspace-local :file-id]) + (let [file-id (get-in state [:workspace-page :file-id]) name (str "Page " (gensym "p")) ordering (count (get-in state [:files file-id :pages])) params {:name name diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 504797d89..a6be266c6 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -582,8 +582,7 @@ (update [_ state] (let [shape (-> (geom/setup-proportions data) (assoc :id id)) - shape (merge canvas-default-attrs shape) - shape (recalculate-shape-canvas-relation state shape)] + shape (merge canvas-default-attrs shape)] (impl-assoc-shape state shape))) ptk/WatchEvent @@ -592,9 +591,8 @@ sid (:session-id state)] (rx/of (commit-changes [{:type :add-canvas :session-id sid - :id id - :shape shape}]) - (select-shape id))))))) + :shape shape + :id id}]))))))) ;; --- Duplicate Selected @@ -653,7 +651,7 @@ ;; --- Select Shapes (By selrect) (defn- impl-try-match-shape - [xf selrect acc {:keys [type id items] :as shape}] + [selrect acc {:keys [type id items] :as shape}] (cond (geom/contained-in? shape selrect) (conj acc id) @@ -667,14 +665,16 @@ (defn impl-match-by-selrect [state selrect] (let [data (:workspace-data state) + match (partial impl-try-match-shape selrect) + shapes (:shapes data) xf (comp (map #(get-in data [:shapes-by-id %])) (remove :hidden) (remove :blocked) (remove #(= :canvas (:type %))) - (map geom/selection-rect)) - match (partial impl-try-match-shape xf selrect) - shapes (:shapes data)] - (reduce match #{} (sequence xf shapes)))) + (map geom/shape->rect-shape) + (map geom/resolve-rotation) + (map geom/shape->rect-shape))] + (transduce xf match #{} shapes))) (def select-shapes-by-current-selrect (ptk/reify ::select-shapes-by-current-selrect @@ -788,34 +788,35 @@ (defn impl-dissoc-shape "Given a shape, removes it from the state." - [state {:keys [id type] :as shape}] - (as-> state $$ - (if (= :canvas type) - (update-in $$ [:workspace-data :canvas] - (fn [items] (vec (remove #(= % id) items)))) - (update-in $$ [:workspace-data :shapes] - (fn [items] (vec (remove #(= % id) items))))) - (update-in $$ [:workspace-data :shapes-by-id] dissoc id))) + [state id] + (-> state + (update-in [:workspace-data :canvas] (fn [items] (filterv #(not= % id) items))) + (update-in [:workspace-data :shapes] (fn [items] (filterv #(not= % id) items))) + (update-in [:workspace-data :shapes-by-id] dissoc id))) + +(defn impl-lookup-shape + [state id] + (get-in state [:workspace-data :shapes-by-id id])) (def delete-selected "Deselect all and remove all selected shapes." (ptk/reify ::delete-selected - ptk/UpdateEvent - (update [_ state] - (let [selected (get-in state [:workspace-local :selected])] - (reduce impl-dissoc-shape state - (map #(get-in state [:workspace-data :shapes-by-id %]) selected)))) - ptk/WatchEvent (watch [_ state stream] - (let [selected (get-in state [:workspace-local :selected]) - session-id (:session-id state) - changes (mapv (fn [id] - {:type :del-shape - :session-id session-id - :id id}) - selected)] - (rx/of (commit-changes changes)))))) + (let [session-id (:session-id state) + lookup-shape #(get-in state [:workspace-data :shapes-by-id %]) + selected (get-in state [:workspace-local :selected]) + + changes (->> selected + (map lookup-shape) + (map (fn [{:keys [type id] :as shape}] + {:type (if (= type :canvas) :del-canvas :del-shape) + :session-id session-id + :id id})))] + (rx/merge + (rx/of deselect-all) + (rx/from (map (fn [id] #(impl-dissoc-shape % id)) selected)) + (rx/of (commit-changes changes))))))) ;; --- Rename Shape @@ -998,7 +999,7 @@ (ptk/reify ::commit-changes ptk/UpdateEvent (update [_ state] - (let [pid (get-in state [:workspace-local :page-id]) + (let [pid (get-in state [:workspace-page :id]) data (get-in state [:pages-data pid])] (update-in state [:pages-data pid] cp/process-changes changes))) @@ -1007,7 +1008,7 @@ (let [page (:workspace-page state) params {:id (:id page) :version (:version page) - :changes changes}] + :changes (vec changes)}] (->> (rp/mutation :update-project-page params) (rx/map shapes-changes-commited)))))) diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 3f9a5497d..79672953b 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -109,7 +109,7 @@ :circle (size-circle shape) :curve (size-path shape) :path (size-path shape) - (size-rect shape))) + shape)) (defn- size-path [{:keys [segments x1 y1 x2 y2] :as shape}] @@ -125,13 +125,6 @@ :width (- maxx minx) :height (- maxy miny))))) -(defn- size-rect - "A specialized function for calculate size - for rect-like shapes." - [{:keys [x1 y1 x2 y2] :as shape}] - (merge shape {:width (- x2 x1) - :height (- y2 y1)})) - (defn- size-circle "A specialized function for calculate size for circle shape." @@ -158,7 +151,6 @@ (defn- assign-proportions-circle [{:as shape}] - (prn "assign-proportions-circle" shape) (assoc shape :proportion 1)) ;; TODO: implement the rest of shapes @@ -265,67 +257,44 @@ (case vid :top-left (-> (gmt/matrix) - (gmt/translate (+ (:x2 shape)) - (+ (:y2 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x2 shape)) - (- (:y2 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x2 shape)) + (+ (:y2 shape))))) :top-right (-> (gmt/matrix) - (gmt/translate (+ (:x1 shape)) - (+ (:y2 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x1 shape)) - (- (:y2 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x1 shape)) + (+ (:y2 shape))))) :top (-> (gmt/matrix) - (gmt/translate (+ (:x1 shape)) - (+ (:y2 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x1 shape)) - (- (:y2 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x1 shape)) + (+ (:y2 shape))))) :bottom-left (-> (gmt/matrix) - (gmt/translate (+ (:x2 shape)) - (+ (:y1 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x2 shape)) - (- (:y1 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x2 shape)) + (+ (:y1 shape))))) :bottom-right (-> (gmt/matrix) - (gmt/translate (+ (:x shape)) - (+ (:y shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x shape)) - (- (:y shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x shape)) + (+ (:y shape))))) :bottom (-> (gmt/matrix) - (gmt/translate (+ (:x1 shape)) - (+ (:y1 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x1 shape)) - (- (:y1 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x1 shape)) + (+ (:y1 shape))))) :right (-> (gmt/matrix) - (gmt/translate (+ (:x1 shape)) - (+ (:y1 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x1 shape)) - (- (:y1 shape)))) - + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x1 shape)) + (+ (:y1 shape))))) :left (-> (gmt/matrix) - (gmt/translate (+ (:x2 shape)) - (+ (:y1 shape))) - (gmt/scale scalex scaley) - (gmt/translate (- (:x2 shape)) - (- (:y1 shape)))))) + (gmt/scale (gpt/point scalex scaley) + (gpt/point (+ (:x2 shape)) + (+ (:y1 shape))))))) (defn resize-shape @@ -479,28 +448,6 @@ :height (- maxy miny) :type :rect})) -;; (defn shapes->rect-shape' -;; [shapes] -;; (let [shapes (mapv shape->rect-shape shapes) -;; total (count shapes)] -;; (loop [idx (int 0) -;; minx js/Number.POSITIVE_INFINITY -;; miny js/Number.POSITIVE_INFINITY -;; maxx js/Number.NEGATIVE_INFINITY -;; maxy js/Number.NEGATIVE_INFINITY] -;; (if (> total idx) -;; (let [{:keys [x1 y1 x2 y2]} (nth shapes idx)] -;; (recur (inc idx) -;; (min minx x1) -;; (min miny y1) -;; (max maxx x2) -;; (max maxy y2))) -;; {:x1 minx -;; :y1 miny -;; :x2 maxx -;; :y2 maxy -;; :type :rect})))) - (defn- rect->rect-shape [{:keys [x y width height] :as shape}] (assoc shape @@ -511,10 +458,10 @@ (defn- path->rect-shape [{:keys [segments] :as shape}] - (let [minx (apply min (map :x segments)) - miny (apply min (map :y segments)) - maxx (apply max (map :x segments)) - maxy (apply max (map :y segments))] + (let [minx (transduce (map :x) min segments) + miny (transduce (map :y) min segments) + maxx (transduce (map :x) max segments) + maxy (transduce (map :y) max segments)] (assoc shape :x1 minx :y1 miny @@ -566,6 +513,7 @@ tr (gpt/transform [(+ x width) y] mx) bl (gpt/transform [x (+ y height)] mx) br (gpt/transform [(+ x width) (+ y height)] mx) + ;; TODO: replace apply with transduce (performance) minx (apply min (map :x [tl tr bl br])) maxx (apply max (map :x [tl tr bl br])) miny (apply min (map :y [tl tr bl br])) @@ -576,9 +524,6 @@ :width (- maxx minx) :height (- maxy miny)))) - ;; :x2 (+ minx (- maxx minx)) - ;; :y2 (+ miny (- maxy miny))))) - (defn- transform-circle [{:keys [cx cy rx ry] :as shape} xfmt] (let [{:keys [x1 y1 x2 y2]} (shape->rect-shape shape) @@ -587,6 +532,7 @@ bl (gpt/transform [x1 y2] xfmt) br (gpt/transform [x2 y2] xfmt) + ;; TODO: replace apply with transduce (performance) x (apply min (map :x [tl tr bl br])) y (apply min (map :y [tl tr bl br])) maxx (apply max (map :x [tl tr bl br])) @@ -608,30 +554,48 @@ (defn rotation-matrix "Generate a rotation matrix from shape." - [{:keys [x1 y1 rotation] :as shape}] - (let [{:keys [width height]} (size shape) - x-center (+ x1 (/ width 2)) - y-center (+ y1 (/ height 2))] - (-> (gmt/matrix) - ;; (gmt/rotate* rotation (gpt/point x-center y-center))))) - (gmt/translate x-center y-center) - (gmt/rotate rotation) - (gmt/translate (- x-center) (- y-center))))) + [{:keys [x y width height rotation] :as shape}] + (let [cx (+ x (/ width 2)) + cy (+ y (/ height 2))] + (cond-> (gmt/matrix) + (and rotation (pos? rotation)) + (gmt/rotate rotation (gpt/point cx cy))))) -(defn rotate-shape - "Apply the transformation matrix to the shape." +(defn resolve-rotation [shape] - (let [mtx (rotation-matrix (size shape))] - (transform shape mtx))) + (transform shape (rotation-matrix shape))) + +(defn resolve-modifier + [{:keys [modifier-mtx] :as shape}] + (cond-> shape + (gmt/matrix? modifier-mtx) + (transform modifier-mtx))) + +(def ^:private + xf-resolve-shapes + (comp (map shape->rect-shape) + (map resolve-modifier) + (map resolve-rotation) + (map shape->rect-shape))) (defn selection-rect - "Return the selection rect for the shape." - [shape] - (let [modifier (:modifier-mtx shape)] - (-> (shape->rect-shape shape) - (assoc :type :rect :id (:id shape)) - (transform (or modifier (gmt/matrix))) - #_(rotate-shape)))) + "Returns a rect that contains all the shapes and is aware of the + rotation of each shape. Mainly used for multiple selection." + [shapes] + (let [shapes (into [] xf-resolve-shapes shapes) + minx (transduce (map :x1) min shapes) + miny (transduce (map :y1) min shapes) + maxx (transduce (map :x2) max shapes) + maxy (transduce (map :y2) max shapes)] + {:x1 minx + :y1 miny + :x2 maxx + :y2 maxy + :x minx + :y miny + :width (- maxx minx) + :height (- maxy miny) + :type :rect})) ;; --- Helpers diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index 4d8c6869a..966920ec6 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -32,13 +32,6 @@ ;; --- Rect Shape -(defn- rotate - [mt {:keys [x1 y1 x2 y2 width height rotation] :as shape}] - (let [x-center (+ x1 (/ width 2)) - y-center (+ y1 (/ height 2)) - center (gpt/point x-center y-center)] - (gmt/rotate* mt rotation center))) - (mf/defc rect-shape [{:keys [shape] :as props}] (let [{:keys [id rotation modifier-mtx]} shape @@ -49,18 +42,16 @@ {:keys [x y width height]} shape - ;; transform (when (pos? rotation) - ;; (str (rotate (gmt/matrix) shape))) - - transform (str/format "rotate(%s %s %s)" - rotation - (+ x (/ width 2)) - (+ y (/ height 2))) + transform (when (and rotation (pos? rotation)) + (str/format "rotate(%s %s %s)" + rotation + (+ x (/ width 2)) + (+ y (/ height 2)))) props (-> (attrs/extract-style-attrs shape) (assoc :x x :y y - ;; :transform transform + :transform transform :id (str "shape-" id) :width width :height height diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index 49add35a0..b93c93982 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -304,10 +304,10 @@ (mf/defc generic-draw-area [{:keys [shape zoom]}] - (let [{:keys [x1 y1 width height]} (geom/selection-rect shape)] + (let [{:keys [x y width height]} (geom/shape->rect-shape shape)] [:g [:& shapes/shape-wrapper {:shape shape}] - [:rect.main {:x x1 :y y1 + [:rect.main {:x x :y y :width width :height height :stroke-dasharray (str (/ 5.0 zoom) "," (/ 5 zoom)) diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 6d51eabfc..0c94ce186 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -20,9 +20,14 @@ [uxbox.main.streams :as ms] [uxbox.main.workers :as uwrk] [uxbox.util.dom :as dom] - [uxbox.util.geom.point :as gpt])) + [uxbox.util.geom.point :as gpt] + [uxbox.util.geom.matrix :as gmt])) -;; --- Resize Implementation +(defn- apply-zoom + [point] + (gpt/divide point @refs/selected-zoom)) + +;; --- Resize & Rotate (defn- start-resize [vid ids shape] @@ -44,11 +49,7 @@ (apply-grid-alignment [point] (if @refs/selected-alignment (uwrk/align-point point) - (rx/of point))) - - ;; Apply the current zoom factor to the point. - (apply-zoom [point] - (gpt/divide point @refs/selected-zoom))] + (rx/of point)))] (reify ptk/WatchEvent (watch [_ state stream] @@ -78,11 +79,8 @@ (rx/concat (->> ms/mouse-position - ;; (rx/map apply-zoom) - ;; (rx/mapcat apply-grid-alignment) + (rx/map apply-zoom) (rx/with-latest vector ms/mouse-position-ctrl) - ;; (rx/map normalize-proportion-lock) - ;; (rx/mapcat (partial resize shape)) (rx/map (fn [[pos ctrl?]] (let [angle (+ (gpt/angle pos center) 90) angle (if (neg? angle) @@ -93,6 +91,9 @@ (if (< 50 modval) (+ angle (- 90 modval)) (- angle modval)) + angle) + angle (if (= angle 360) + 0 angle)] (dw/update-shape (:id shape) {:rotation angle})))) (rx/take-until stoper))))))) @@ -121,36 +122,35 @@ (mf/defc controls [{:keys [shape zoom on-resize on-rotate] :as props}] - (let [{:keys [x y width height]} shape + (let [{:keys [x y width height rotation]} shape radius (if (> (max width height) handler-size-threshold) 6.0 4.0) - - transform (str/format "rotate(%s %s %s)" - (:rotation shape 0) - (+ (:x shape) (/ (:width shape) 2)) - (+ (:y shape) (/ (:height shape) 2)))] - - [:g.controls #_{:transform transform} + transform (geom/rotation-matrix shape)] + [:g.controls {:transform transform} [:rect.main {:x x :y y :width width :height height :stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom)) :style {:stroke "#31EFB8" :fill "transparent" :stroke-opacity "1"}}] - [:path {:stroke "#31EFB8" - :stroke-opacity "1" - :stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom)) - :fill "transparent" - :d (str/format "M %s %s L %s %s" - (+ x (/ width 2)) - y - (+ x (/ width 2)) - (- y 30))}] - [:& control-item {:class "rotate" - :r (/ radius zoom) - :cx (+ x (/ width 2)) - :on-click on-rotate - :cy (- y 30)}] + (when (fn? on-rotate) + [:* + [:path {:stroke "#31EFB8" + :stroke-opacity "1" + :stroke-dasharray (str (/ 8.0 zoom) "," (/ 5 zoom)) + :fill "transparent" + :d (str/format "M %s %s L %s %s" + (+ x (/ width 2)) + y + (+ x (/ width 2)) + (- y 30))}] + + [:& control-item {:class "rotate" + :r (/ radius zoom) + :cx (+ x (/ width 2)) + :on-click on-rotate + :cy (- y 30)}]]) + [:& control-item {:class "top" :on-click #(on-resize :top %) :r (/ radius zoom) @@ -232,21 +232,10 @@ ;; TODO: add specs for clarity -(mf/defc multiple-selection-handlers - [{:keys [shapes selected zoom] :as props}] - (let [shape (->> shapes - (map #(geom/selection-rect %)) - (geom/shapes->rect-shape) - (geom/selection-rect)) - on-resize #(do (dom/stop-propagation %2) - (st/emit! (start-resize %1 selected shape)))] - [:& controls {:shape shape - :zoom zoom - :on-resize on-resize}])) (mf/defc text-edition-selection-handlers [{:keys [shape zoom] :as props}] - (let [{:keys [x y width height] :as shape} (geom/selection-rect shape)] + (let [{:keys [x y width height] :as shape} shape] [:g.controls [:rect.main {:x x :y y :width width @@ -257,13 +246,25 @@ :stroke-opacity "1" :fill "transparent"}}]])) +(mf/defc multiple-selection-handlers + [{:keys [shapes selected zoom] :as props}] + (let [shape (geom/selection-rect shapes) + on-resize #(do (dom/stop-propagation %2) + (st/emit! (start-resize %1 selected shape)))] + [:& controls {:shape shape + :zoom zoom + :on-resize on-resize}])) + (mf/defc single-selection-handlers [{:keys [shape zoom] :as props}] + (prn "single-selection-handlers" shape) (let [on-resize #(do (dom/stop-propagation %2) (st/emit! (start-resize %1 #{(:id shape)} shape))) on-rotate #(do (dom/stop-propagation %) - #_(st/emit! (start-rotate shape))) - shape (geom/selection-rect shape)] + (st/emit! (start-rotate shape))) + modifier (:modifier-mtx shape) + shape (-> (geom/shape->rect-shape shape) + (geom/transform (or modifier (gmt/matrix))))] [:& controls {:shape shape :zoom zoom :on-rotate on-rotate @@ -272,7 +273,11 @@ (mf/defc selection-handlers [{:keys [selected edition zoom] :as props}] (let [data (mf/deref refs/workspace-data) - shapes (map #(get-in data [:shapes-by-id %]) selected) + ;; We need remove posible nil values because on shape + ;; deletion many shape will reamin selected and deleted + ;; in the same time for small instant of time + shapes (->> (map #(get-in data [:shapes-by-id %]) selected) + (remove nil?)) num (count shapes) {:keys [id type] :as shape} (first shapes)] (cond diff --git a/frontend/src/uxbox/util/geom/matrix.cljs b/frontend/src/uxbox/util/geom/matrix.cljs index 3ac18958e..9c3acd5fd 100644 --- a/frontend/src/uxbox/util/geom/matrix.cljs +++ b/frontend/src/uxbox/util/geom/matrix.cljs @@ -13,7 +13,7 @@ ;; --- Matrix Impl -(defrecord Matrix [a b c d tx ty]) +(defrecord Matrix [a b c d e f]) (defprotocol ICoerce "Matrix coersion protocol." @@ -22,7 +22,7 @@ (extend-type Matrix cljs.core/IDeref (-deref [v] - (mapv #(get v %) [:a :c :b :d :tx :ty])) + (mapv #(get v %) [:a :b :c :d :e :f])) Object (toString [v] @@ -39,37 +39,26 @@ cljs.core/PersistentVector (-matrix [v] - (let [[a b c d tx ty] v] - (Matrix. a b c d tx ty))) + (let [[a b c d e f] v] + (Matrix. a b c d e f))) cljs.core/IndexedSeq (-matrix [v] - (let [[a b c d tx ty] v] - (Matrix. a b c d tx ty)))) + (let [[a b c d e f] v] + (Matrix. a b c d e f)))) (defn multiply - ([m om] - (let [a1 (:a m) - b1 (:b m) - c1 (:c m) - d1 (:d m) - a2 (:a om) - b2 (:b om) - c2 (:c om) - d2 (:d om) - tx1 (:tx m) - ty1 (:ty m) - tx2 (:tx om) - ty2 (:ty om)] - (Matrix. - (+ (* a2 a1) (* c2 c1)) - (+ (* a2 b1) (* c2 d1)) - (+ (* b2 a1) (* d2 c1)) - (+ (* b2 b1) (* d2 d1)) - (+ tx1 (* tx2 a1) (* ty2 c1)) - (+ ty1 (* tx2 b1) (* ty2 d1))))) - ([m om & others] - (reduce multiply (multiply m om) others))) + ([m1 m2] + (Matrix. + (+ (* (:a m1) (:a m2)) (* (:c m1) (:b m2))) + (+ (* (:b m1) (:a m2)) (* (:d m1) (:b m2))) + (+ (* (:a m1) (:c m2)) (* (:c m1) (:d m2))) + (+ (* (:b m1) (:c m2)) (* (:d m1) (:d m2))) + (+ (* (:a m1) (:e m2)) (* (:c m1) (:f m2)) (:e m1)) + (+ (* (:b m1) (:e m2)) (* (:d m1) (:f m2)) (:f m1)))) + + ([m1 m2 & others] + (reduce multiply (multiply m1 m2) others))) (defn ^boolean matrix? "Return true if `v` is Matrix instance." @@ -82,30 +71,98 @@ (Matrix. 1 0 0 1 0 0)) ([v] (-matrix v)) - ([a b c d tx ty] - (Matrix. a b c d tx ty))) + ([a b c d e f] + (Matrix. a b c d e f))) (defn translate-matrix - ([pt] - (let [pt (gpt/point pt)] - (Matrix. 1 0 0 1 (:x pt) (:y pt)))) - ([x y] - (translate-matrix (gpt/point x y)))) + [pt] + (let [pt (gpt/point pt)] + (Matrix. 1 0 0 1 (:x pt) (:y pt)))) (defn scale-matrix - ([s] - (Matrix. s 0 0 s 0 0)) - ([sx sy] - (Matrix. sx 0 0 sy 0 0))) + [s] + (let [pt (gpt/point s)] + (Matrix. (:x pt) 0 0 (:y pt) 0 0))) (defn rotate-matrix [a] (let [a (mth/radians a)] - (Matrix. (mth/cos a) - (mth/sin a) - (- (mth/sin a)) - (mth/cos a) - 0 0))) + (Matrix. + (mth/cos a) + (mth/sin a) + (- (mth/sin a)) + (mth/cos a) + 0 + 0))) + +;; OLD +;; (defn rotate +;; "Apply rotation transformation to the matrix." +;; ([m angle] +;; (multiply m (rotate-matrix angle))) +;; ([m angle center] +;; (multiply m +;; (translate-matrix center) +;; (rotate-matrix angle) +;; (translate-matrix (gpt/negate center))))) + +;; -- ROTATE +;; r = radians(r) +;; const cos = Math.cos(r) +;; const sin = Math.sin(r) +;; +;; const { a, b, c, d, e, f } = this +;; +;; this.a = a * cos - b * sin +;; this.b = b * cos + a * sin +;; this.c = c * cos - d * sin +;; this.d = d * cos + c * sin +;; this.e = e * cos - f * sin + cy * sin - cx * cos + cx +;; this.f = f * cos + e * sin - cx * sin - cy * cos + cy + +;; (defn rotate +;; ([m angle] (rotate m angle (gpt/point 0 0))) +;; ([m angle center] +;; (let [{:keys [a b c d e f]} m +;; {cx :x cy :y} center +;; r (mth/radians angle) +;; cos (mth/cos r) +;; sin (mth/sin r) +;; a' (- (* a cos) (* b sin)) +;; b' (+ (* b cos) (* a sin)) +;; c' (- (* c cos) (* d sin)) +;; d' (+ (* d cos) (* c sin)) +;; e' (+ (- (* e cos) (* f sin)) +;; (- (* cy sin) (* cx cos)) +;; cx) +;; f' (+ (- (+ (* f cos) (* e sin)) +;; (* cx sin) +;; (* cy cos)) +;; cy)] +;; (Matrix. a' b' c' d' e' f')))) + + +;; export function rotate (angle, cx, cy) { +;; const cosAngle = cos(angle) +;; const sinAngle = sin(angle) +;; const rotationMatrix = { +;; a: cosAngle, +;; c: -sinAngle, +;; e: 0, +;; b: sinAngle, +;; d: cosAngle, +;; f: 0 +;; } +;; if (isUndefined(cx) || isUndefined(cy)) { +;; return rotationMatrix +;; } + +;; return transform([ +;; translate(cx, cy), +;; rotationMatrix, +;; translate(-cx, -cy) +;; ]) +;; } (defn rotate "Apply rotation transformation to the matrix." @@ -113,35 +170,12 @@ (multiply m (rotate-matrix angle))) ([m angle center] (multiply m - (translate-matrix center) - (rotate-matrix angle) - (translate-matrix (gpt/negate center))))) + (translate-matrix center) + (rotate-matrix angle) + (translate-matrix (gpt/negate center))))) -(defn rotate* - ([m angle] - (let [center (gpt/point 0 0)] - (rotate m angle center))) - ([m angle center] - (let [angle (mth/radians angle) - x (:x center) - y (:y center) - cos (mth/cos angle) - sin (mth/sin angle) - nsin (- sin) - tx (- x (+ (* x cos)) (* y sin)) - ty (- y (- (* x sin)) (* y cos)) - a (+ (* cos (:a m)) (* sin (:c m))) - b (+ (* cos (:b m)) (* sin (:d m))) - c (+ (* nsin (:a m)) (* cos (:c m))) - d (+ (* nsin (:b m)) (* cos (:d m))) - tx' (+ (:tx m) (* tx (:a m)) (* ty (:c m))) - ty' (+ (:ty m) (* tx (:b m)) (* ty (:d m)))] - (Matrix. a b c d tx' ty')))) - -(defn scale - "Apply scale transformation to the matrix." - ([m v] (scale m v v)) - ([m vx vy] (multiply m (scale-matrix vx vy)))) +;; TODO: temporal backward compatibility +(def rotate* rotate) ;; ([m v] (scale m v v)) ;; ([m x y] @@ -151,38 +185,77 @@ ;; :b (* (:b m) y) ;; :d (* (:d m) y)))) + + + ;; scaleO (x, y = x, cx = 0, cy = 0) { + ;; // Support uniform scaling + ;; if (arguments.length === 3) { + ;; cy = cx + ;; cx = y + ;; y = x + ;; } + + ;; const { a, b, c, d, e, f } = this + + ;; this.a = a * x + ;; this.b = b * y + ;; this.c = c * x + ;; this.d = d * y + ;; this.e = e * x - cx * x + cx + ;; this.f = f * y - cy * y + cy + + ;; return this + ;; } + +;; (defn scale +;; "Apply scale transformation to the matrix." +;; ([m x] (scale m x x)) +;; ([m x y] +;; (let [{:keys [a b c d e f]} m +;; cx 0 +;; cy 0 +;; a' (* a x) +;; b' (* b y) +;; c' (* c x) +;; d' (* d y) +;; e' (+ cx (- (* e x) +;; (* cx x))) +;; f' (+ cy (- (* f y) +;; (* cy y)))] +;; (Matrix. a' b' c' d' e f)))) + +(defn scale + "Apply scale transformation to the matrix." + ([m s] (multiply m (scale-matrix s))) + ([m s c] + (multiply m + (translate-matrix c) + (scale-matrix s) + (translate-matrix (gpt/negate c))))) + (defn translate "Apply translate transformation to the matrix." - ([m pt] - (multiply m (translate-matrix pt))) - ([m x y] - (translate m (gpt/point x y)))) - - ;; ([m pt] - ;; (let [pt (gpt/point pt)] - ;; (assoc m - ;; :tx (+ (:tx m) (* (:x pt) (:a m)) (* (:y pt) (:b m))) - ;; :ty (+ (:ty m) (* (:x pt) (:c m)) (* (:y pt) (:d m)))))) - ;; ([m x y] - ;; (translate m (gpt/point x y)))) + [m pt] + (let [pt (gpt/point pt)] + (multiply m (translate-matrix pt)))) (defn ^boolean invertible? - [{:keys [a b c d tx ty] :as m}] + [{:keys [a b c d e f] :as m}] (let [det (- (* a d) (* c b))] (and (not (mth/nan? det)) - (mth/finite? tx) - (mth/finite? ty)))) + (mth/finite? e) + (mth/finite? f)))) (defn invert - [{:keys [a b c d tx ty] :as m}] + [{:keys [a b c d e f] :as m}] (when (invertible? m) (let [det (- (* a d) (* c b))] (Matrix. (/ d det) (/ (- b) det) (/ (- c) det) (/ a det) - (/ (- (* c ty) (* d tx)) det) - (/ (- (* b tx) (* a ty)) det))))) + (/ (- (* c f) (* d e)) det) + (/ (- (* b e) (* a f)) det))))) ;; --- Transit Adapter diff --git a/frontend/src/uxbox/util/geom/point.cljs b/frontend/src/uxbox/util/geom/point.cljs index 9a04c9e01..2d46b4742 100644 --- a/frontend/src/uxbox/util/geom/point.cljs +++ b/frontend/src/uxbox/util/geom/point.cljs @@ -116,7 +116,6 @@ If the second vector is not provided, the angle will be measured from x-axis." ([p] - (-> (mth/atan2 (:y p) (:x p)) (mth/degrees))) ([p center] @@ -162,10 +161,10 @@ (defn transform "Transform a point applying a matrix transfomation." - [pt {:keys [a b c d tx ty] :as m}] + [pt {:keys [a b c d e f] :as m}] (let [{:keys [x y]} (point pt)] - (Point. (+ (* x a) (* y c) tx) - (+ (* x b) (* y d) ty)))) + (Point. (+ (* x a) (* y c) e) + (+ (* x b) (* y d) f)))) ;; --- Transit Adapter diff --git a/frontend/tools.clj b/frontend/tools.clj index 8c0f7d353..af5f6e6c0 100644 --- a/frontend/tools.clj +++ b/frontend/tools.clj @@ -201,6 +201,7 @@ :pprint-config false :load-warninged-code false :auto-testing false + :reload-dependents true :reload-clj-files true :css-dirs ["resources/public/css"] :ring-server-options {:port 3449 :host "0.0.0.0"}