mirror of
https://github.com/penpot/penpot.git
synced 2025-06-07 16:31:40 +02:00
⚡ Add huge optimization to the transform-points-matrix
it reduces the 90% overhead of this function; in an relative comparison the same execution is reduced from 350ms to 18ms
This commit is contained in:
parent
4d3064ba6d
commit
a15a2010b6
1 changed files with 129 additions and 152 deletions
|
@ -5,10 +5,7 @@
|
||||||
;; Copyright (c) KALEIDOS INC
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
(ns app.common.geom.shapes.transforms
|
(ns app.common.geom.shapes.transforms
|
||||||
#?(:clj (:import (org.la4j Matrix LinearAlgebra))
|
|
||||||
:cljs (:import goog.math.Matrix))
|
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [app.common.exceptions :as ex])
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
|
@ -19,8 +16,8 @@
|
||||||
[app.common.geom.shapes.path :as gpa]
|
[app.common.geom.shapes.path :as gpa]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.types.modifiers :as ctm]
|
[app.common.record :as cr]
|
||||||
[app.common.uuid :as uuid]))
|
[app.common.types.modifiers :as ctm]))
|
||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
|
@ -40,10 +37,6 @@
|
||||||
y (dm/get-prop selrect :y)
|
y (dm/get-prop selrect :y)
|
||||||
w (dm/get-prop selrect :width)
|
w (dm/get-prop selrect :width)
|
||||||
h (dm/get-prop selrect :height)
|
h (dm/get-prop selrect :height)
|
||||||
x1 (dm/get-prop selrect :x1)
|
|
||||||
y1 (dm/get-prop selrect :y1)
|
|
||||||
x2 (dm/get-prop selrect :x2)
|
|
||||||
y2 (dm/get-prop selrect :y2)
|
|
||||||
dx (dm/get-prop pt :x)
|
dx (dm/get-prop pt :x)
|
||||||
dy (dm/get-prop pt :y)]
|
dy (dm/get-prop pt :y)]
|
||||||
|
|
||||||
|
@ -60,19 +53,32 @@
|
||||||
(mapv #(gpt/add % move-vec) points)
|
(mapv #(gpt/add % move-vec) points)
|
||||||
points))
|
points))
|
||||||
|
|
||||||
;; FIXME: revisit performance
|
;; FIXME: deprecated
|
||||||
(defn move-position-data
|
(defn move-position-data
|
||||||
([position-data {:keys [x y]}]
|
[position-data delta]
|
||||||
(move-position-data position-data x y))
|
|
||||||
|
|
||||||
([position-data dx dy]
|
|
||||||
(when (some? position-data)
|
(when (some? position-data)
|
||||||
(cond->> position-data
|
(let [dx (dm/get-prop delta :x)
|
||||||
(d/num? dx dy)
|
dy (dm/get-prop delta :y)]
|
||||||
|
(if (d/num? dx dy)
|
||||||
(mapv #(-> %
|
(mapv #(-> %
|
||||||
(update :x + dx)
|
(update :x + dx)
|
||||||
(update :y + dy)))))))
|
(update :y + dy))
|
||||||
|
position-data)
|
||||||
|
position-data))))
|
||||||
|
|
||||||
|
(defn transform-position-data
|
||||||
|
[position-data transform]
|
||||||
|
(when (some? position-data)
|
||||||
|
(let [dx (dm/get-prop transform :e)
|
||||||
|
dy (dm/get-prop transform :f)]
|
||||||
|
(if (d/num? dx dy)
|
||||||
|
(mapv #(-> %
|
||||||
|
(update :x + dx)
|
||||||
|
(update :y + dy))
|
||||||
|
position-data)
|
||||||
|
position-data))))
|
||||||
|
|
||||||
|
;; FIXME: revist usage of mutability
|
||||||
(defn move
|
(defn move
|
||||||
"Move the shape relatively to its current
|
"Move the shape relatively to its current
|
||||||
position applying the provided delta."
|
position applying the provided delta."
|
||||||
|
@ -89,7 +95,7 @@
|
||||||
(update :points move-points mvec)
|
(update :points move-points mvec)
|
||||||
(d/update-when :x d/safe+ dx)
|
(d/update-when :x d/safe+ dx)
|
||||||
(d/update-when :y d/safe+ dy)
|
(d/update-when :y d/safe+ dy)
|
||||||
(d/update-when :position-data move-position-data dx dy)
|
(d/update-when :position-data move-position-data mvec)
|
||||||
(cond-> (= :bool type) (update :bool-content gpa/move-content mvec))
|
(cond-> (= :bool type) (update :bool-content gpa/move-content mvec))
|
||||||
(cond-> (= :path type) (update :content gpa/move-content mvec)))))
|
(cond-> (= :path type) (update :content gpa/move-content mvec)))))
|
||||||
|
|
||||||
|
@ -100,28 +106,13 @@
|
||||||
[shape pos]
|
[shape pos]
|
||||||
(let [x (dm/get-prop pos :x)
|
(let [x (dm/get-prop pos :x)
|
||||||
y (dm/get-prop pos :y)
|
y (dm/get-prop pos :y)
|
||||||
sr (dm/get-prop shape selrect)
|
sr (dm/get-prop shape :selrect)
|
||||||
px (dm/get-prop sr :x)
|
px (dm/get-prop sr :x)
|
||||||
py (dm/get-prop sr :y)
|
py (dm/get-prop sr :y)
|
||||||
dx (- (d/check-num x) px)
|
dx (- (d/check-num x) px)
|
||||||
dy (- (d/check-num y) py)]
|
dy (- (d/check-num y) py)]
|
||||||
(move shape (gpt/point dx dy))))
|
(move shape (gpt/point dx dy))))
|
||||||
|
|
||||||
; ---- Geometric operations
|
|
||||||
|
|
||||||
(defn- calculate-height
|
|
||||||
"Calculates the height of a parallelogram given by the points"
|
|
||||||
[[p1 _ _ p4]]
|
|
||||||
|
|
||||||
(-> (gpt/to-vec p4 p1)
|
|
||||||
(gpt/length)))
|
|
||||||
|
|
||||||
(defn- calculate-width
|
|
||||||
"Calculates the width of a parallelogram given by the points"
|
|
||||||
[[p1 p2 _ _]]
|
|
||||||
(-> (gpt/to-vec p1 p2)
|
|
||||||
(gpt/length)))
|
|
||||||
|
|
||||||
;; --- Transformation matrix operations
|
;; --- Transformation matrix operations
|
||||||
|
|
||||||
(defn transform-matrix
|
(defn transform-matrix
|
||||||
|
@ -160,6 +151,7 @@
|
||||||
(dm/str (transform-matrix shape params))
|
(dm/str (transform-matrix shape params))
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
|
;; FIXME: performance
|
||||||
(defn inverse-transform-matrix
|
(defn inverse-transform-matrix
|
||||||
([shape]
|
([shape]
|
||||||
(let [shape-center (or (gco/shape->center shape)
|
(let [shape-center (or (gco/shape->center shape)
|
||||||
|
@ -174,6 +166,7 @@
|
||||||
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
|
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
|
||||||
(gmt/translate (gpt/negate center)))))
|
(gmt/translate (gpt/negate center)))))
|
||||||
|
|
||||||
|
;; FIXME: move to geom rect?
|
||||||
(defn transform-rect
|
(defn transform-rect
|
||||||
"Transform a rectangles and changes its attributes"
|
"Transform a rectangles and changes its attributes"
|
||||||
[rect matrix]
|
[rect matrix]
|
||||||
|
@ -183,137 +176,127 @@
|
||||||
(grc/points->rect points)))
|
(grc/points->rect points)))
|
||||||
|
|
||||||
(defn transform-points-matrix
|
(defn transform-points-matrix
|
||||||
"Calculate the transform matrix to convert from the selrect to the points bounds
|
[selrect [d1 d2 _ d4]]
|
||||||
TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)"
|
|
||||||
[{:keys [x1 y1 x2 y2]} [d1 d2 _ d4]]
|
|
||||||
;; If the coordinates are very close to zero (but not zero) the rounding can mess with the
|
;; If the coordinates are very close to zero (but not zero) the rounding can mess with the
|
||||||
;; transforms. So we round to zero the values
|
;; transforms. So we round to zero the values
|
||||||
(let [x1 (mth/round-to-zero x1)
|
(let [x1 (mth/round-to-zero (dm/get-prop selrect :x1))
|
||||||
y1 (mth/round-to-zero y1)
|
y1 (mth/round-to-zero (dm/get-prop selrect :y1))
|
||||||
x2 (mth/round-to-zero x2)
|
x2 (mth/round-to-zero (dm/get-prop selrect :x2))
|
||||||
y2 (mth/round-to-zero y2)
|
y2 (mth/round-to-zero (dm/get-prop selrect :y2))
|
||||||
d1x (mth/round-to-zero (:x d1))
|
|
||||||
d1y (mth/round-to-zero (:y d1))
|
|
||||||
d2x (mth/round-to-zero (:x d2))
|
|
||||||
d2y (mth/round-to-zero (:y d2))
|
|
||||||
d4x (mth/round-to-zero (:x d4))
|
|
||||||
d4y (mth/round-to-zero (:y d4))]
|
|
||||||
#?(:clj
|
|
||||||
;; NOTE: the source matrix may not be invertible we can't
|
|
||||||
;; calculate the transform, so on exception we return `nil`
|
|
||||||
(ex/ignoring
|
|
||||||
(let [target-points-matrix
|
|
||||||
(->> (list d1x d2x d4x
|
|
||||||
d1y d2y d4y
|
|
||||||
1 1 1)
|
|
||||||
(into-array Double/TYPE)
|
|
||||||
(Matrix/from1DArray 3 3))
|
|
||||||
|
|
||||||
source-points-matrix
|
det (+ (- (* (- y1 y2) x1)
|
||||||
(->> (list x1 x2 x1
|
(* (- y1 y2) x2))
|
||||||
y1 y1 y2
|
(* (- y1 y1) x1))]
|
||||||
1 1 1)
|
|
||||||
(into-array Double/TYPE)
|
|
||||||
(Matrix/from1DArray 3 3))
|
|
||||||
|
|
||||||
;; May throw an exception if the matrix is not invertible
|
(when-not (zero? det)
|
||||||
source-points-matrix-inv
|
(let [ma0 (mth/round-to-zero (dm/get-prop d1 :x))
|
||||||
(.. source-points-matrix
|
ma1 (mth/round-to-zero (dm/get-prop d2 :x))
|
||||||
(withInverter LinearAlgebra/GAUSS_JORDAN)
|
ma2 (mth/round-to-zero (dm/get-prop d4 :x))
|
||||||
(inverse))
|
ma3 (mth/round-to-zero (dm/get-prop d1 :y))
|
||||||
|
ma4 (mth/round-to-zero (dm/get-prop d2 :y))
|
||||||
|
ma5 (mth/round-to-zero (dm/get-prop d4 :y))
|
||||||
|
|
||||||
transform-jvm
|
mb0 (/ (- y1 y2) det)
|
||||||
(.. target-points-matrix
|
mb1 (/ (- x1 x2) det)
|
||||||
(multiply source-points-matrix-inv))]
|
mb2 (/ (- (* x2 y2) (* x1 y1)) det)
|
||||||
|
mb3 (/ (- y2 y1) det)
|
||||||
|
mb4 (/ (- x1 x1) det)
|
||||||
|
mb5 (/ (- (* x1 y1) (* x1 y2)) det)
|
||||||
|
mb6 (/ (- y1 y1) det)
|
||||||
|
mb7 (/ (- x2 x1) det)
|
||||||
|
mb8 (/ (- (* x1 y1) (* x2 y1)) det)]
|
||||||
|
|
||||||
(gmt/matrix (.get transform-jvm 0 0)
|
(gmt/matrix (+ (* ma0 mb0)
|
||||||
(.get transform-jvm 1 0)
|
(* ma1 mb3)
|
||||||
(.get transform-jvm 0 1)
|
(* ma2 mb6))
|
||||||
(.get transform-jvm 1 1)
|
(+ (* ma3 mb0)
|
||||||
(.get transform-jvm 0 2)
|
(* ma4 mb3)
|
||||||
(.get transform-jvm 1 2))))
|
(* ma5 mb6))
|
||||||
|
(+ (* ma0 mb1)
|
||||||
|
(* ma1 mb4)
|
||||||
|
(* ma2 mb7))
|
||||||
|
(+ (* ma3 mb1)
|
||||||
|
(* ma4 mb4)
|
||||||
|
(* ma5 mb7))
|
||||||
|
(+ (* ma0 mb2)
|
||||||
|
(* ma1 mb5)
|
||||||
|
(* ma2 mb8))
|
||||||
|
(+ (* ma3 mb2)
|
||||||
|
(* ma4 mb5)
|
||||||
|
(* ma5 mb8)))))))
|
||||||
|
|
||||||
:cljs
|
(defn calculate-selrect
|
||||||
(let [target-points-matrix
|
[points center]
|
||||||
(Matrix. #js [#js [d1x d2x d4x]
|
|
||||||
#js [d1y d2y d4y]
|
|
||||||
#js [ 1 1 1]])
|
|
||||||
|
|
||||||
source-points-matrix
|
(let [p1 (nth points 0)
|
||||||
(Matrix. #js [#js [x1 x2 x1]
|
p2 (nth points 1)
|
||||||
#js [y1 y1 y2]
|
p4 (nth points 3)
|
||||||
#js [ 1 1 1]])
|
|
||||||
|
|
||||||
;; returns nil if not invertible
|
width (mth/hypot
|
||||||
source-points-matrix-inv (.getInverse source-points-matrix)
|
(- (dm/get-prop p2 :x)
|
||||||
|
(dm/get-prop p1 :x))
|
||||||
|
(- (dm/get-prop p2 :y)
|
||||||
|
(dm/get-prop p1 :y)))
|
||||||
|
|
||||||
;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)
|
height (mth/hypot
|
||||||
transform-js
|
(- (dm/get-prop p1 :x)
|
||||||
(when source-points-matrix-inv
|
(dm/get-prop p4 :x))
|
||||||
(.multiply target-points-matrix source-points-matrix-inv))]
|
(- (dm/get-prop p1 :y)
|
||||||
|
(dm/get-prop p4 :y)))]
|
||||||
|
|
||||||
(when transform-js
|
(grc/center->rect center width height)))
|
||||||
(gmt/matrix (.getValueAt transform-js 0 0)
|
|
||||||
(.getValueAt transform-js 1 0)
|
|
||||||
(.getValueAt transform-js 0 1)
|
|
||||||
(.getValueAt transform-js 1 1)
|
|
||||||
(.getValueAt transform-js 0 2)
|
|
||||||
(.getValueAt transform-js 1 2)))))))
|
|
||||||
|
|
||||||
(defn calculate-geometry
|
(defn calculate-transform
|
||||||
[points]
|
[points center selrect]
|
||||||
(let [width (calculate-width points)
|
(let [transform (transform-points-matrix selrect points)
|
||||||
height (calculate-height points)
|
|
||||||
|
|
||||||
;; FIXME: looks redundant, we can convert points to rect directly
|
|
||||||
center (gco/points->center points)
|
|
||||||
sr (grc/center->rect center width height)
|
|
||||||
|
|
||||||
points-transform-mtx (transform-points-matrix sr points)
|
|
||||||
|
|
||||||
;; Calculate the transform by move the transformation to the center
|
;; Calculate the transform by move the transformation to the center
|
||||||
transform
|
transform
|
||||||
(when points-transform-mtx
|
(when (some? transform)
|
||||||
(gmt/multiply
|
(-> (gmt/translate-matrix-neg center)
|
||||||
(gmt/translate-matrix (gpt/negate center))
|
(gmt/multiply! transform)
|
||||||
points-transform-mtx
|
(gmt/multiply! (gmt/translate-matrix center))))]
|
||||||
(gmt/translate-matrix center)))
|
|
||||||
|
|
||||||
transform-inverse (when transform (gmt/inverse transform))
|
|
||||||
|
|
||||||
;; There is a rounding error when the matrix returned have float point values
|
;; There is a rounding error when the matrix returned have float point values
|
||||||
;; when the matrix is unit we return a "pure" matrix so we don't accumulate
|
;; when the matrix is unit we return a "pure" matrix so we don't accumulate
|
||||||
;; rounding problems
|
;; rounding problems
|
||||||
[transform transform-inverse]
|
(when ^boolean (gmt/matrix? transform)
|
||||||
(if (gmt/unit? transform)
|
(if ^boolean (gmt/unit? transform)
|
||||||
[(gmt/matrix) (gmt/matrix)]
|
gmt/base
|
||||||
[transform transform-inverse])]
|
transform))))
|
||||||
|
|
||||||
[sr transform transform-inverse]))
|
(defn calculate-geometry
|
||||||
|
[points]
|
||||||
|
(let [center (gco/points->center points)
|
||||||
|
selrect (calculate-selrect points center)
|
||||||
|
transform (calculate-transform points center selrect)]
|
||||||
|
[selrect transform (when (some? transform) (gmt/inverse transform))]))
|
||||||
|
|
||||||
(defn- adjust-shape-flips
|
(defn- adjust-shape-flips
|
||||||
"After some tranformations the flip-x/flip-y flags can change we need
|
"After some tranformations the flip-x/flip-y flags can change we need
|
||||||
to check this before adjusting the selrect"
|
to check this before adjusting the selrect"
|
||||||
[shape points]
|
[shape points]
|
||||||
|
(let [points' (dm/get-prop shape :points)
|
||||||
|
p0' (nth points' 0)
|
||||||
|
p0 (nth points 0)
|
||||||
|
|
||||||
(let [points' (:points shape)
|
;; FIXME: unroll and remove point allocation here
|
||||||
|
xv1 (gpt/to-vec p0' (nth points' 1))
|
||||||
xv1 (gpt/to-vec (nth points' 0) (nth points' 1))
|
xv2 (gpt/to-vec p0 (nth points 1))
|
||||||
xv2 (gpt/to-vec (nth points 0) (nth points 1))
|
|
||||||
dot-x (gpt/dot xv1 xv2)
|
dot-x (gpt/dot xv1 xv2)
|
||||||
|
|
||||||
yv1 (gpt/to-vec (nth points' 0) (nth points' 3))
|
yv1 (gpt/to-vec p0' (nth points' 3))
|
||||||
yv2 (gpt/to-vec (nth points 0) (nth points 3))
|
yv2 (gpt/to-vec p0 (nth points 3))
|
||||||
dot-y (gpt/dot yv1 yv2)]
|
dot-y (gpt/dot yv1 yv2)]
|
||||||
|
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
(neg? dot-x)
|
(neg? dot-x)
|
||||||
(-> (update :flip-x not)
|
(-> (update :flip-x not)
|
||||||
(update :rotation -))
|
(cr/update! :rotation -))
|
||||||
|
|
||||||
(neg? dot-y)
|
(neg? dot-y)
|
||||||
(-> (update :flip-y not)
|
(-> (update :flip-y not)
|
||||||
(update :rotation -)))))
|
(cr/update! :rotation -)))))
|
||||||
|
|
||||||
(defn- apply-transform-move
|
(defn- apply-transform-move
|
||||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||||
|
@ -386,7 +369,7 @@
|
||||||
"Given a new set of points transformed, set up the rectangle so it keeps
|
"Given a new set of points transformed, set up the rectangle so it keeps
|
||||||
its properties. We adjust de x,y,width,height and create a custom transform"
|
its properties. We adjust de x,y,width,height and create a custom transform"
|
||||||
[shape transform-mtx]
|
[shape transform-mtx]
|
||||||
(if (gmt/move? transform-mtx)
|
(if ^boolean (gmt/move? transform-mtx)
|
||||||
(apply-transform-move shape transform-mtx)
|
(apply-transform-move shape transform-mtx)
|
||||||
(apply-transform-generic shape transform-mtx)))
|
(apply-transform-generic shape transform-mtx)))
|
||||||
|
|
||||||
|
@ -477,21 +460,16 @@
|
||||||
(transform-shape modifiers))))
|
(transform-shape modifiers))))
|
||||||
|
|
||||||
([shape modifiers]
|
([shape modifiers]
|
||||||
(letfn [(apply-modifiers
|
(if (and (some? modifiers) (not (ctm/empty? modifiers)))
|
||||||
[shape modifiers]
|
|
||||||
(if (ctm/empty? modifiers)
|
|
||||||
shape
|
|
||||||
(let [transform (ctm/modifiers->transform modifiers)]
|
(let [transform (ctm/modifiers->transform modifiers)]
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
(and (some? transform) (not= uuid/zero (:id shape))) ;; Never transform the root frame
|
(and (some? transform)
|
||||||
|
(not (cph/root? shape)))
|
||||||
(apply-transform transform)
|
(apply-transform transform)
|
||||||
|
|
||||||
(ctm/has-structure? modifiers)
|
(ctm/has-structure? modifiers)
|
||||||
(ctm/apply-structure-modifiers modifiers)))))]
|
(ctm/apply-structure-modifiers modifiers)))
|
||||||
|
shape)))
|
||||||
(cond-> shape
|
|
||||||
(and (some? modifiers) (not (ctm/empty? modifiers)))
|
|
||||||
(apply-modifiers modifiers)))))
|
|
||||||
|
|
||||||
(defn apply-objects-modifiers
|
(defn apply-objects-modifiers
|
||||||
([objects modifiers]
|
([objects modifiers]
|
||||||
|
@ -532,7 +510,6 @@
|
||||||
(gco/transform-points mtx)
|
(gco/transform-points mtx)
|
||||||
(grc/points->rect)))
|
(grc/points->rect)))
|
||||||
|
|
||||||
|
|
||||||
(declare apply-group-modifiers)
|
(declare apply-group-modifiers)
|
||||||
|
|
||||||
(defn apply-children-modifiers
|
(defn apply-children-modifiers
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue