mirror of
https://github.com/penpot/penpot.git
synced 2025-03-15 17:21:17 -05: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
|
||||
|
||||
(ns app.common.geom.shapes.transforms
|
||||
#?(:clj (:import (org.la4j Matrix LinearAlgebra))
|
||||
:cljs (:import goog.math.Matrix))
|
||||
(:require
|
||||
#?(:clj [app.common.exceptions :as ex])
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
|
@ -19,8 +16,8 @@
|
|||
[app.common.geom.shapes.path :as gpa]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.uuid :as uuid]))
|
||||
[app.common.record :as cr]
|
||||
[app.common.types.modifiers :as ctm]))
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
|
@ -40,10 +37,6 @@
|
|||
y (dm/get-prop selrect :y)
|
||||
w (dm/get-prop selrect :width)
|
||||
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)
|
||||
dy (dm/get-prop pt :y)]
|
||||
|
||||
|
@ -60,19 +53,32 @@
|
|||
(mapv #(gpt/add % move-vec) points)
|
||||
points))
|
||||
|
||||
;; FIXME: revisit performance
|
||||
;; FIXME: deprecated
|
||||
(defn move-position-data
|
||||
([position-data {:keys [x y]}]
|
||||
(move-position-data position-data x y))
|
||||
|
||||
([position-data dx dy]
|
||||
(when (some? position-data)
|
||||
(cond->> position-data
|
||||
(d/num? dx dy)
|
||||
(mapv #(-> %
|
||||
[position-data delta]
|
||||
(when (some? position-data)
|
||||
(let [dx (dm/get-prop delta :x)
|
||||
dy (dm/get-prop delta :y)]
|
||||
(if (d/num? dx dy)
|
||||
(mapv #(-> %
|
||||
(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
|
||||
"Move the shape relatively to its current
|
||||
position applying the provided delta."
|
||||
|
@ -89,7 +95,7 @@
|
|||
(update :points move-points mvec)
|
||||
(d/update-when :x d/safe+ dx)
|
||||
(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-> (= :path type) (update :content gpa/move-content mvec)))))
|
||||
|
||||
|
@ -100,28 +106,13 @@
|
|||
[shape pos]
|
||||
(let [x (dm/get-prop pos :x)
|
||||
y (dm/get-prop pos :y)
|
||||
sr (dm/get-prop shape selrect)
|
||||
sr (dm/get-prop shape :selrect)
|
||||
px (dm/get-prop sr :x)
|
||||
py (dm/get-prop sr :y)
|
||||
dx (- (d/check-num x) px)
|
||||
dy (- (d/check-num y) py)]
|
||||
(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
|
||||
|
||||
(defn transform-matrix
|
||||
|
@ -160,6 +151,7 @@
|
|||
(dm/str (transform-matrix shape params))
|
||||
"")))
|
||||
|
||||
;; FIXME: performance
|
||||
(defn inverse-transform-matrix
|
||||
([shape]
|
||||
(let [shape-center (or (gco/shape->center shape)
|
||||
|
@ -174,6 +166,7 @@
|
|||
(gmt/multiply (:transform-inverse shape (gmt/matrix)))
|
||||
(gmt/translate (gpt/negate center)))))
|
||||
|
||||
;; FIXME: move to geom rect?
|
||||
(defn transform-rect
|
||||
"Transform a rectangles and changes its attributes"
|
||||
[rect matrix]
|
||||
|
@ -183,137 +176,127 @@
|
|||
(grc/points->rect points)))
|
||||
|
||||
(defn transform-points-matrix
|
||||
"Calculate the transform matrix to convert from the selrect to the points bounds
|
||||
TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)"
|
||||
[{:keys [x1 y1 x2 y2]} [d1 d2 _ d4]]
|
||||
[selrect [d1 d2 _ d4]]
|
||||
;; 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
|
||||
(let [x1 (mth/round-to-zero x1)
|
||||
y1 (mth/round-to-zero y1)
|
||||
x2 (mth/round-to-zero x2)
|
||||
y2 (mth/round-to-zero 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))
|
||||
(let [x1 (mth/round-to-zero (dm/get-prop selrect :x1))
|
||||
y1 (mth/round-to-zero (dm/get-prop selrect :y1))
|
||||
x2 (mth/round-to-zero (dm/get-prop selrect :x2))
|
||||
y2 (mth/round-to-zero (dm/get-prop selrect :y2))
|
||||
|
||||
source-points-matrix
|
||||
(->> (list x1 x2 x1
|
||||
y1 y1 y2
|
||||
1 1 1)
|
||||
(into-array Double/TYPE)
|
||||
(Matrix/from1DArray 3 3))
|
||||
det (+ (- (* (- y1 y2) x1)
|
||||
(* (- y1 y2) x2))
|
||||
(* (- y1 y1) x1))]
|
||||
|
||||
;; May throw an exception if the matrix is not invertible
|
||||
source-points-matrix-inv
|
||||
(.. source-points-matrix
|
||||
(withInverter LinearAlgebra/GAUSS_JORDAN)
|
||||
(inverse))
|
||||
(when-not (zero? det)
|
||||
(let [ma0 (mth/round-to-zero (dm/get-prop d1 :x))
|
||||
ma1 (mth/round-to-zero (dm/get-prop d2 :x))
|
||||
ma2 (mth/round-to-zero (dm/get-prop d4 :x))
|
||||
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
|
||||
(.. target-points-matrix
|
||||
(multiply source-points-matrix-inv))]
|
||||
mb0 (/ (- y1 y2) det)
|
||||
mb1 (/ (- x1 x2) det)
|
||||
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)
|
||||
(.get transform-jvm 1 0)
|
||||
(.get transform-jvm 0 1)
|
||||
(.get transform-jvm 1 1)
|
||||
(.get transform-jvm 0 2)
|
||||
(.get transform-jvm 1 2))))
|
||||
(gmt/matrix (+ (* ma0 mb0)
|
||||
(* ma1 mb3)
|
||||
(* ma2 mb6))
|
||||
(+ (* ma3 mb0)
|
||||
(* ma4 mb3)
|
||||
(* 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
|
||||
(let [target-points-matrix
|
||||
(Matrix. #js [#js [d1x d2x d4x]
|
||||
#js [d1y d2y d4y]
|
||||
#js [ 1 1 1]])
|
||||
(defn calculate-selrect
|
||||
[points center]
|
||||
|
||||
source-points-matrix
|
||||
(Matrix. #js [#js [x1 x2 x1]
|
||||
#js [y1 y1 y2]
|
||||
#js [ 1 1 1]])
|
||||
(let [p1 (nth points 0)
|
||||
p2 (nth points 1)
|
||||
p4 (nth points 3)
|
||||
|
||||
;; returns nil if not invertible
|
||||
source-points-matrix-inv (.getInverse source-points-matrix)
|
||||
width (mth/hypot
|
||||
(- (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)
|
||||
transform-js
|
||||
(when source-points-matrix-inv
|
||||
(.multiply target-points-matrix source-points-matrix-inv))]
|
||||
height (mth/hypot
|
||||
(- (dm/get-prop p1 :x)
|
||||
(dm/get-prop p4 :x))
|
||||
(- (dm/get-prop p1 :y)
|
||||
(dm/get-prop p4 :y)))]
|
||||
|
||||
(when transform-js
|
||||
(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)))))))
|
||||
(grc/center->rect center width height)))
|
||||
|
||||
(defn calculate-geometry
|
||||
[points]
|
||||
(let [width (calculate-width 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)
|
||||
(defn calculate-transform
|
||||
[points center selrect]
|
||||
(let [transform (transform-points-matrix selrect points)
|
||||
|
||||
;; Calculate the transform by move the transformation to the center
|
||||
transform
|
||||
(when points-transform-mtx
|
||||
(gmt/multiply
|
||||
(gmt/translate-matrix (gpt/negate center))
|
||||
points-transform-mtx
|
||||
(gmt/translate-matrix center)))
|
||||
(when (some? transform)
|
||||
(-> (gmt/translate-matrix-neg center)
|
||||
(gmt/multiply! transform)
|
||||
(gmt/multiply! (gmt/translate-matrix center))))]
|
||||
|
||||
transform-inverse (when transform (gmt/inverse transform))
|
||||
;; 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
|
||||
;; rounding problems
|
||||
(when ^boolean (gmt/matrix? transform)
|
||||
(if ^boolean (gmt/unit? transform)
|
||||
gmt/base
|
||||
transform))))
|
||||
|
||||
;; 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
|
||||
;; rounding problems
|
||||
[transform transform-inverse]
|
||||
(if (gmt/unit? transform)
|
||||
[(gmt/matrix) (gmt/matrix)]
|
||||
[transform transform-inverse])]
|
||||
|
||||
[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
|
||||
"After some tranformations the flip-x/flip-y flags can change we need
|
||||
to check this before adjusting the selrect"
|
||||
[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))
|
||||
xv2 (gpt/to-vec p0 (nth points 1))
|
||||
dot-x (gpt/dot xv1 xv2)
|
||||
|
||||
xv1 (gpt/to-vec (nth points' 0) (nth points' 1))
|
||||
xv2 (gpt/to-vec (nth points 0) (nth points 1))
|
||||
dot-x (gpt/dot xv1 xv2)
|
||||
|
||||
yv1 (gpt/to-vec (nth points' 0) (nth points' 3))
|
||||
yv2 (gpt/to-vec (nth points 0) (nth points 3))
|
||||
dot-y (gpt/dot yv1 yv2)]
|
||||
yv1 (gpt/to-vec p0' (nth points' 3))
|
||||
yv2 (gpt/to-vec p0 (nth points 3))
|
||||
dot-y (gpt/dot yv1 yv2)]
|
||||
|
||||
(cond-> shape
|
||||
(neg? dot-x)
|
||||
(-> (update :flip-x not)
|
||||
(update :rotation -))
|
||||
(cr/update! :rotation -))
|
||||
|
||||
(neg? dot-y)
|
||||
(-> (update :flip-y not)
|
||||
(update :rotation -)))))
|
||||
(cr/update! :rotation -)))))
|
||||
|
||||
(defn- apply-transform-move
|
||||
"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
|
||||
its properties. We adjust de x,y,width,height and create a custom transform"
|
||||
[shape transform-mtx]
|
||||
(if (gmt/move? transform-mtx)
|
||||
(if ^boolean (gmt/move? transform-mtx)
|
||||
(apply-transform-move shape transform-mtx)
|
||||
(apply-transform-generic shape transform-mtx)))
|
||||
|
||||
|
@ -477,21 +460,16 @@
|
|||
(transform-shape modifiers))))
|
||||
|
||||
([shape modifiers]
|
||||
(letfn [(apply-modifiers
|
||||
[shape modifiers]
|
||||
(if (ctm/empty? modifiers)
|
||||
shape
|
||||
(let [transform (ctm/modifiers->transform modifiers)]
|
||||
(cond-> shape
|
||||
(and (some? transform) (not= uuid/zero (:id shape))) ;; Never transform the root frame
|
||||
(apply-transform transform)
|
||||
(if (and (some? modifiers) (not (ctm/empty? modifiers)))
|
||||
(let [transform (ctm/modifiers->transform modifiers)]
|
||||
(cond-> shape
|
||||
(and (some? transform)
|
||||
(not (cph/root? shape)))
|
||||
(apply-transform transform)
|
||||
|
||||
(ctm/has-structure? modifiers)
|
||||
(ctm/apply-structure-modifiers modifiers)))))]
|
||||
|
||||
(cond-> shape
|
||||
(and (some? modifiers) (not (ctm/empty? modifiers)))
|
||||
(apply-modifiers modifiers)))))
|
||||
(ctm/has-structure? modifiers)
|
||||
(ctm/apply-structure-modifiers modifiers)))
|
||||
shape)))
|
||||
|
||||
(defn apply-objects-modifiers
|
||||
([objects modifiers]
|
||||
|
@ -532,7 +510,6 @@
|
|||
(gco/transform-points mtx)
|
||||
(grc/points->rect)))
|
||||
|
||||
|
||||
(declare apply-group-modifiers)
|
||||
|
||||
(defn apply-children-modifiers
|
||||
|
|
Loading…
Add table
Reference in a new issue