0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-08 13:01:24 -05:00

🎉 New stretch method

This commit is contained in:
alonso.torres 2020-04-22 18:24:54 +02:00
parent b73958efd0
commit 8886db7453
7 changed files with 179 additions and 89 deletions

View file

@ -348,12 +348,12 @@
(ptk/type? ::initialize-page %))
stream)
notifier (->> stream
(rx/filter (ptk/type? ::commit-changes))
(rx/filter (ptk/type? ::common/commit-changes))
(rx/debounce 2000)
(rx/merge stoper))]
(rx/merge
(->> stream
(rx/filter (ptk/type? ::commit-changes))
(rx/filter (ptk/type? ::common/commit-changes))
(rx/map deref)
(rx/buffer-until notifier)
(rx/map vec)

View file

@ -11,6 +11,8 @@
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.math :as mth]
[uxbox.util.data :as d]
[uxbox.util.debug :as debug]
[uxbox.main.data.helpers :as helpers]))
;; --- Relative Movement
@ -149,6 +151,14 @@
maxy (apply max (map :y segments))]
(gpt/point (/ (+ minx maxx) 2) (/ (+ miny maxy) 2))))
(defn center->rect
"Creates a rect given a center and a width and height"
[center width height]
{:x (- (:x center) (/ width 2))
:y (- (:y center) (/ height 2))
:width width
:height height})
;; --- Proportions
(declare assign-proportions-path)
@ -343,7 +353,8 @@
(let [points [(gpt/point x y)
(gpt/point (+ x width) y)
(gpt/point (+ x width) (+ y height))
(gpt/point x (+ y height))]]
(gpt/point x (+ y height))
(gpt/point x y)]]
(-> shape
(assoc :type :path)
(assoc :segments points))))
@ -409,6 +420,15 @@
(transform-rect shape xfmt))
shape))
(defn center-transform [shape matrix]
(let [shape-center (center shape)]
(-> shape
(transform
(-> (gmt/matrix)
(gmt/translate shape-center)
(gmt/multiply matrix)
(gmt/translate (gpt/negate shape-center)))))))
(defn- transform-rect
[{:keys [x y width height] :as shape} mx]
(let [tl (gpt/transform (gpt/point x y) mx)
@ -613,6 +633,39 @@
:type :rect}]
(overlaps? shape selrect)))
(defn calculate-rec-path-skew-angle
[path-shape]
(let [p1 (get-in path-shape [:segments 2])
p2 (get-in path-shape [:segments 3])
p3 (get-in path-shape [:segments 4])
v1 (gpt/to-vec p1 p2)
v2 (gpt/to-vec p2 p3)]
(- 90 (gpt/angle-with-other v1 v2))))
(defn calculate-rec-path-height
"Calculates the height of a paralelogram given by the path"
[path-shape]
(let [p1 (get-in path-shape [:segments 2])
p2 (get-in path-shape [:segments 3])
p3 (get-in path-shape [:segments 4])
v1 (gpt/to-vec p1 p2)
v2 (gpt/to-vec p2 p3)
angle (gpt/angle-with-other v1 v2)]
(* (gpt/length v2) (mth/sin (mth/radians angle)))))
(defn calculate-rec-path-rotation
[center path-shape1 path-shape2]
(let [p1 (get-in path-shape1 [:segments 0])
p2 (get-in path-shape2 [:segments 0])
v1 (gpt/to-vec center p1)
v2 (gpt/to-vec center p2)
rot-angle (gpt/angle-with-other v1 v2)
rot-sign (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)]
(* rot-sign rot-angle)))
(defn transform-shape-point
"Transform a point around the shape center"
[point shape transform]
@ -624,19 +677,6 @@
transform
(gmt/translate-matrix (gpt/negate shape-center)))))))
(defn- add-rotate-transform [shape rotation]
(let [rotation (or rotation 0)]
(-> shape
(update :transform #(gmt/multiply (gmt/rotate-matrix rotation) (or % (gmt/matrix))))
(update :transform-inverse #(gmt/multiply (or % (gmt/matrix)) (gmt/rotate-matrix (- rotation)))))))
(defn- add-stretch-transform [shape stretch]
(let [stretch (or stretch (gpt/point 1 1))]
(-> shape
(update :transform #(gmt/multiply (gmt/scale-matrix stretch) (or % (gmt/matrix))))
(update :transform-inverse #(gmt/multiply (or % (gmt/matrix)) (gmt/scale-matrix (gpt/inverse stretch)))))))
(defn- transform-apply-modifiers
[shape]
(let [ds-modifier (:displacement-modifier shape (gmt/matrix))
@ -667,19 +707,32 @@
(gmt/multiply (:transform shape (gmt/matrix)))
(gmt/translate (gpt/negate shape-center)))))))
(defn calculate-stretch
[shape-path shape]
(let [{:keys [width height] :as selrect} (shape->rect-shape shape-path)
{width' :width height' :height :as selrect'} (selection-rect [shape])
shape-path-size (gpt/point width height)
shape-size (gpt/point width' height')]
(gpt/divide shape-path-size shape-size)))
(defn rect-path-dimensions [rect-path]
(let [seg (:segments rect-path)
[width height] (mapv (fn [[c1 c2]] (gpt/distance c1 c2)) (take 2 (d/zip seg (rest seg))))]
{:width width
:height height}))
(defn calculate-stretch [shape-path transform-inverse]
(let [shape-center (center shape-path)
shape-path-temp (transform
shape-path
(-> (gmt/matrix)
(gmt/translate shape-center)
(gmt/multiply transform-inverse)
(gmt/translate (gpt/negate shape-center))))
shape-path-temp-rec (shape->rect-shape shape-path-temp)
shape-path-temp-dim (rect-path-dimensions shape-path-temp)]
(gpt/divide (gpt/point (:width shape-path-temp-rec) (:height shape-path-temp-rec))
(gpt/point (:width shape-path-temp-dim) (:height shape-path-temp-dim)))))
(defn transform-selrect
[frame shape]
(-> (shape->rect-shape (transform-apply-modifiers shape))
(update :x - (:x frame 0))
(update :y - (:y frame 0))))
(-> shape
(transform-apply-modifiers)
(translate-to-frame frame)
(shape->rect-shape)))
(defn dissoc-modifiers [shape]
(-> shape
@ -692,58 +745,74 @@
(defn transform-rect-shape
[shape]
(let [shape-path (transform-apply-modifiers shape)
shape-path-center (center shape-path)
(let [;; Apply modifiers to the rect as a path so we have the end shape expected
shape-path (transform-apply-modifiers shape)
shape-center (center shape-path)
shape-transform-inverse' (-> (gmt/matrix)
(gmt/translate shape-path-center)
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/multiply (gmt/rotate-matrix (- (:rotation-modifier shape 0))))
(gmt/translate (gpt/negate shape-path-center)))
;; Reverse the current transformation stack to get the base rectangle
shape-path-temp (center-transform shape-path (:transform-inverse shape (gmt/matrix)))
shape-path-temp-dim (rect-path-dimensions shape-path-temp)
shape-path-temp-rec (shape->rect-shape shape-path-temp)
;; Revert the transformation so we can calculate the rectangle properties: x, y, width, height
changes (-> shape-path
(transform shape-transform-inverse')
(path->rect-shape)
(select-keys [:x :y :width :height]))
;; This rectangle is the new data for the current rectangle. We want to change our rectangle
;; to have this width, height, x, y
rec (center->rect shape-center (:width shape-path-temp-dim) (:height shape-path-temp-dim))
rec-path (rect->path rec)
;; The next matrix is a series of transformations we have to do to the previous rec so that
;; after applying them the end result is the `shape-path-temp`
;; This is compose of three transformations: skew, resize and rotation
stretch-matrix (gmt/matrix)
skew-angle (calculate-rec-path-skew-angle shape-path-temp)
stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0))
h1 (calculate-rec-path-height shape-path-temp)
h2 (calculate-rec-path-height (center-transform rec-path stretch-matrix))
stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point 1 (/ h1 h2))))
rotation-angle (calculate-rec-path-rotation shape-center (center-transform rec-path stretch-matrix) shape-path-temp)
stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix)
;; This is the inverse to be able to remove the transformation
stretch-matrix-inverse (-> (gmt/matrix)
(gmt/scale (gpt/point 1 (/ h2 h1)))
(gmt/skew (- skew-angle) 0)
(gmt/rotate (- rotation-angle)))
;; Merges the rect values and updates de transformation matrix before calculating the stretch
new-shape (-> shape
(merge changes)
(add-rotate-transform (:rotation-modifier shape 0)))
;; Calculate the stretch deformation with the resize and rotation aplied
stretch (calculate-stretch shape-path (-> new-shape dissoc-modifiers))]
(merge rec)
(update :transform #(gmt/multiply (or % (gmt/matrix)) stretch-matrix))
(update :transform-inverse #(gmt/multiply stretch-matrix-inverse (or % (gmt/matrix)))))]
(-> new-shape
(add-stretch-transform stretch))))
new-shape))
(defn transform-path-shape
[shape]
(transform-apply-modifiers shape)
;; TODO: Addapt for paths is not working
#_(let [shape-path (transform-apply-modifiers shape)
shape-path-center (center shape-path)
shape-path-center (center shape-path)
shape-transform-inverse' (-> (gmt/matrix)
(gmt/translate shape-path-center)
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/multiply (gmt/rotate-matrix (- (:rotation-modifier shape 0))))
(gmt/translate (gpt/negate shape-path-center)))]
(-> shape-path
(transform shape-transform-inverse')
(add-rotate-transform (:rotation-modifier shape 0)))))
shape-transform-inverse' (-> (gmt/matrix)
(gmt/translate shape-path-center)
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
(gmt/multiply (gmt/rotate-matrix (- (:rotation-modifier shape 0))))
(gmt/translate (gpt/negate shape-path-center)))]
(-> shape-path
(transform shape-transform-inverse')
(add-rotate-transform (:rotation-modifier shape 0)))))
(defn transform-shape
"Transform the shape properties given the modifiers"
([shape] (transform-shape nil shape))
([frame shape]
(let [new-shape (cond
(#{:path :curve} (:type shape)) (transform-path-shape shape)
:else (transform-rect-shape shape))]
(let [new-shape (case (:type shape)
:path (transform-path-shape shape)
:curve (transform-path-shape shape)
(transform-rect-shape shape))]
(-> new-shape
(update :x - (:x frame 0))
(update :y - (:y frame 0))
(translate-to-frame frame)
(update :rotation #(mod (+ % (:rotation-modifier shape)) 360))
(dissoc-modifiers)))))

View file

@ -9,7 +9,10 @@
[cuerdas.core :as str]
[rumext.alpha :as mf]
[uxbox.main.geom :as geom]
[uxbox.util.debug :refer [debug?] ]))
[uxbox.util.debug :as debug]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.debug :refer [debug?]]))
(defn fix [num]
(when num (.toFixed num 2)))
@ -20,7 +23,11 @@
(when (debug? :bounding-boxes)
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
selrect (geom/transform-selrect frame shape)]
selrect (geom/transform-selrect frame shape)
shape-path (-> shape
(geom/transform-apply-modifiers)
(geom/translate-to-frame frame))
shape-center (geom/center shape-path)]
[:g
[:text {:x (:x selrect)
:y (- (:y selrect) 5)
@ -30,12 +37,8 @@
:stroke-width 0.1}
(str/format "%s - (%s, %s)" (str/slice (str (:id shape)) 0 8) (fix (:x shape)) (fix (:y shape)))]
[:text {:x (-> selrect geom/center :x)
:y (-> selrect geom/center :y)
:fill "red"
:font-size 15
:text-anchor "middle"}
(str/format "%s°" (fix (+ (:rotation shape) (:rotation-modifier shape))))]
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}]
[:rect {:x (:x selrect)
:y (:y selrect)
:width (:width selrect)

View file

@ -111,6 +111,9 @@
not-found))
not-found coll)))
(defn zip [col1 col2]
(map vector col1 col2))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Numbers Parsing
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -3,7 +3,7 @@
(def debug-options #{:bounding-boxes :group :events #_:simple-selection})
(defonce ^:dynamic *debug* (atom #{}))
(defonce ^:dynamic *debug* (atom #{:bounding-boxes}))
(defn debug-all! [] (reset! *debug* debug-options))
(defn debug-none! [] (reset! *debug* #{}))

View file

@ -79,6 +79,16 @@
0
0))))
(defn skew-matrix
([angle-x angle-y point]
(multiply (translate-matrix point)
(skew-matrix angle-y angle-y)
(translate-matrix (gpt/negate point))))
([angle-x angle-y]
(let [m1 (mth/tan (mth/radians angle-x))
m2 (mth/tan (mth/radians angle-y))]
(Matrix. 1 m2 m1 1 0 0))))
(defn rotate
"Apply rotation transformation to the matrix."
([m angle]
@ -98,6 +108,12 @@
[m pt]
(multiply m (translate-matrix pt)))
(defn skew
"Apply translate transformation to the matrix."
([m angle-x angle-y]
(multiply m (skew-matrix angle-x angle-y)))
([m angle-x angle-y p]
(multiply m (skew-matrix angle-x angle-y p))))
;; --- Transit Adapter
@ -110,23 +126,3 @@
(t/read-handler
(fn [value]
(map->Matrix value))))
;; Calculates the delta vector to move the figure when scaling after rotation
;; https://math.stackexchange.com/questions/1449672/determine-shift-between-scaled-rotated-object-and-additional-scale-step
(defn correct-rotation [handler lx ly kx ky angle]
(let [[s1 s2 s3]
;; Different sign configurations change the anchor corner
(cond
(#{:right :bottom :bottom-right} handler) [-1 1 1]
(#{:left :top :top-left} handler) [1 -1 1]
(#{:bottom-left} handler) [-1 -1 -1]
(#{:top-right} handler) [1 1 -1])
rad (* (or angle 0) (/ Math/PI 180))
kx' (* (/ (- kx 1.) 2.) lx)
ky' (* (/ (- ky 1.) 2.) ly)
dx (+ (* s3 (* kx' (- 1 (Math/cos rad))))
(* ky' (Math/sin rad)))
dy (+ (* (- s3) (* ky' (- 1 (Math/cos rad))))
(* kx' (Math/sin rad)))]
(translate-matrix
(gpt/point (* s1 dx) (* s2 dy)))))

View file

@ -171,3 +171,22 @@
(fn [value]
(map->Point value))))
;; Vector functions
(defn to-vec [p1 p2]
(subtract p2 p1))
(defn dot [{x1 :x y1 :y} {x2 :x y2 :y}]
(+ (* x1 x2) (* y1 y2)))
(defn unit [v]
(let [v-length (length v)]
(divide v (point v-length v-length))))
(defn project [v1 v2]
(let [v2-unit (unit v2)
scalar-projection (dot v1 (unit v2))]
(multiply
v2-unit
(point scalar-projection scalar-projection))))