0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-12 18:18:24 -05:00

🚧 More work on rotation related stuff.

This commit is contained in:
Andrey Antukh 2020-01-15 17:59:55 +01:00
parent 79a91605d3
commit 72d92c419f
9 changed files with 333 additions and 299 deletions

View file

@ -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

View file

@ -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))))))

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"}