mirror of
https://github.com/penpot/penpot.git
synced 2025-06-07 21:11:38 +02:00
✨ Calculate selrect for bezier curves
This commit is contained in:
parent
f3cce1904c
commit
d8ab3473bf
10 changed files with 212 additions and 79 deletions
|
@ -39,7 +39,10 @@
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(throw (ex-info "Invalid arguments" {:v v}))))
|
(throw (ex-info "Invalid arguments" {:v v}))))
|
||||||
([x y] (Point. x y)))
|
([x y]
|
||||||
|
;;(assert (not (nil? x)))
|
||||||
|
;;(assert (not (nil? y)))
|
||||||
|
(Point. x y)))
|
||||||
|
|
||||||
(defn add
|
(defn add
|
||||||
"Returns the addition of the supplied value to both
|
"Returns the addition of the supplied value to both
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
[app.common.geom.shapes.common :as gco]
|
[app.common.geom.shapes.common :as gco]
|
||||||
[app.common.geom.shapes.transforms :as gtr]
|
[app.common.geom.shapes.transforms :as gtr]
|
||||||
[app.common.geom.shapes.rect :as gpr]
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
|
[app.common.geom.shapes.path :as gsp]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.data :as d]))
|
[app.common.data :as d]))
|
||||||
|
|
||||||
|
@ -299,3 +300,7 @@
|
||||||
(defn transform-matrix [shape] (gtr/transform-matrix shape))
|
(defn transform-matrix [shape] (gtr/transform-matrix shape))
|
||||||
(defn transform-point-center [point center transform] (gtr/transform-point-center point center transform))
|
(defn transform-point-center [point center transform] (gtr/transform-point-center point center transform))
|
||||||
(defn transform-rect [rect mtx] (gtr/transform-rect rect mtx))
|
(defn transform-rect [rect mtx] (gtr/transform-rect rect mtx))
|
||||||
|
|
||||||
|
;; PATHS
|
||||||
|
(defn content->points [content] (gsp/content->points content))
|
||||||
|
(defn content->selrect [content] (gsp/content->selrect content))
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes.path :as gpa]
|
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.data :as d]))
|
[app.common.data :as d]))
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes.rect :as gpr]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.data :as d]))
|
[app.common.data :as d]))
|
||||||
|
|
||||||
|
@ -20,4 +21,124 @@
|
||||||
segments)
|
segments)
|
||||||
|
|
||||||
(defn content->points [content]
|
(defn content->points [content]
|
||||||
(map #(gpt/point (-> % :param :x) (-> % :param :y)) content))
|
(mapv #(gpt/point (-> % :params :x) (-> % :params :y)) content))
|
||||||
|
|
||||||
|
;; https://medium.com/@Acegikmo/the-ever-so-lovely-b%C3%A9zier-curve-eb27514da3bf
|
||||||
|
;; https://en.wikipedia.org/wiki/Bernstein_polynomial
|
||||||
|
(defn curve-values
|
||||||
|
"Parametric equation for cubic beziers. Given a start and end and
|
||||||
|
two intermediate points returns points for values of t.
|
||||||
|
If you draw t on a plane you got the bezier cube"
|
||||||
|
[start end h1 h2 t]
|
||||||
|
|
||||||
|
(let [t2 (* t t) ;; t square
|
||||||
|
t3 (* t2 t) ;; t cube
|
||||||
|
|
||||||
|
start-v (+ (- t3) (* 3 t2) (* -3 t) 1)
|
||||||
|
h1-v (+ (* 3 t3) (* -6 t2) (* 3 t))
|
||||||
|
h2-v (+ (* -3 t3) (* 3 t2))
|
||||||
|
end-v t3
|
||||||
|
|
||||||
|
coord-v (fn [coord]
|
||||||
|
(+ (* (coord start) start-v)
|
||||||
|
(* (coord h1) h1-v)
|
||||||
|
(* (coord h2) h2-v)
|
||||||
|
(* (coord end) end-v)))]
|
||||||
|
|
||||||
|
(gpt/point (coord-v :x) (coord-v :y))))
|
||||||
|
|
||||||
|
;; https://pomax.github.io/bezierinfo/#extremities
|
||||||
|
(defn curve-extremities
|
||||||
|
"Given a cubic bezier cube finds its roots in t. This are the extremities
|
||||||
|
if we calculate its values for x, y we can find a bounding box for the curve."
|
||||||
|
[start end h1 h2]
|
||||||
|
|
||||||
|
(let [coords [[(:x start) (:x h1) (:x h2) (:x end)]
|
||||||
|
[(:y start) (:y h1) (:y h2) (:y end)]]
|
||||||
|
|
||||||
|
coord->tvalue
|
||||||
|
(fn [[c0 c1 c2 c3]]
|
||||||
|
|
||||||
|
(let [a (+ (* -3 c0) (* 9 c1) (* -9 c2) (* 3 c3))
|
||||||
|
b (+ (* 6 c0) (* -12 c1) (* 6 c2))
|
||||||
|
c (+ (* 3 c1) (* -3 c0))
|
||||||
|
|
||||||
|
sqrt-b2-4ac (mth/sqrt (- (* b b) (* 4 a c)))]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(and (mth/almost-zero? a)
|
||||||
|
(not (mth/almost-zero? b)))
|
||||||
|
;; When the term a is close to zero we have a linear equation
|
||||||
|
[(/ (- c) b)]
|
||||||
|
|
||||||
|
;; If a is not close to zero return the two roots for a cuadratic
|
||||||
|
(not (mth/almost-zero? a))
|
||||||
|
[(/ (+ (- b) sqrt-b2-4ac)
|
||||||
|
(* 2 a))
|
||||||
|
(/ (- (- b) sqrt-b2-4ac)
|
||||||
|
(* 2 a))]
|
||||||
|
|
||||||
|
;; If a and b close to zero we can't find a root for a constant term
|
||||||
|
:else
|
||||||
|
[])))]
|
||||||
|
(->> coords
|
||||||
|
(mapcat coord->tvalue)
|
||||||
|
|
||||||
|
;; Only values in the range [0, 1] are valid
|
||||||
|
(filter #(and (>= % 0) (<= % 1)))
|
||||||
|
|
||||||
|
;; Pass t-values to actual points
|
||||||
|
(map #(curve-values start end h1 h2 %)))
|
||||||
|
))
|
||||||
|
|
||||||
|
(defn command->point
|
||||||
|
([command] (command->point command nil))
|
||||||
|
([{params :params} coord]
|
||||||
|
(let [prefix (if coord (name coord) "")
|
||||||
|
xkey (keyword (str prefix "x"))
|
||||||
|
ykey (keyword (str prefix "y"))
|
||||||
|
x (get params xkey)
|
||||||
|
y (get params ykey)]
|
||||||
|
(gpt/point x y))))
|
||||||
|
|
||||||
|
(defn content->selrect [content]
|
||||||
|
(let [calc-extremities
|
||||||
|
(fn [command prev]
|
||||||
|
(case (:command command)
|
||||||
|
:move-to [(command->point command)]
|
||||||
|
|
||||||
|
;; If it's a line we add the beginning point and endpoint
|
||||||
|
:line-to [(command->point prev)
|
||||||
|
(command->point command)]
|
||||||
|
|
||||||
|
;; We return the bezier extremities
|
||||||
|
:curve-to (d/concat
|
||||||
|
[(command->point prev)
|
||||||
|
(command->point command)]
|
||||||
|
(curve-extremities (command->point prev)
|
||||||
|
(command->point command)
|
||||||
|
(command->point command :c1)
|
||||||
|
(command->point command :c2)))))
|
||||||
|
|
||||||
|
extremities (mapcat calc-extremities
|
||||||
|
content
|
||||||
|
(d/concat [nil] content))]
|
||||||
|
|
||||||
|
(gpr/points->selrect extremities)))
|
||||||
|
|
||||||
|
(defn transform-content [content transform]
|
||||||
|
(let [set-tr (fn [params px py]
|
||||||
|
(let [tr-point (-> (gpt/point (get params px) (get params py))
|
||||||
|
(gpt/transform transform))]
|
||||||
|
(assoc params
|
||||||
|
px (:x tr-point)
|
||||||
|
py (:y tr-point))))
|
||||||
|
|
||||||
|
transform-params
|
||||||
|
(fn [{:keys [x y c1x c1y c2x c2y] :as params}]
|
||||||
|
(cond-> params
|
||||||
|
(not (nil? x)) (set-tr :x :y)
|
||||||
|
(not (nil? c1x)) (set-tr :c1x :c1y)
|
||||||
|
(not (nil? c2x)) (set-tr :c2x :c2y)))]
|
||||||
|
|
||||||
|
(mapv #(update % :params transform-params) content)))
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes.path :as gpa]
|
|
||||||
[app.common.geom.shapes.common :as gco]
|
[app.common.geom.shapes.common :as gco]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.data :as d]))
|
[app.common.data :as d]))
|
||||||
|
|
|
@ -89,7 +89,11 @@
|
||||||
:else scale))
|
:else scale))
|
||||||
|
|
||||||
|
|
||||||
(defn modifiers->transform [current-transform center modifiers]
|
(defn modifiers->transform
|
||||||
|
([center modifiers]
|
||||||
|
(modifiers->transform (gmt/matrix) center modifiers))
|
||||||
|
|
||||||
|
([current-transform center modifiers]
|
||||||
(let [ds-modifier (:displacement modifiers (gmt/matrix))
|
(let [ds-modifier (:displacement modifiers (gmt/matrix))
|
||||||
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
|
{res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1))
|
||||||
|
|
||||||
|
@ -121,7 +125,7 @@
|
||||||
|
|
||||||
;; Displacement
|
;; Displacement
|
||||||
(gmt/multiply ds-modifier))]
|
(gmt/multiply ds-modifier))]
|
||||||
transform))
|
transform)))
|
||||||
|
|
||||||
(defn- calculate-skew-angle
|
(defn- calculate-skew-angle
|
||||||
"Calculates the skew angle of the paralelogram given by the points"
|
"Calculates the skew angle of the paralelogram given by the points"
|
||||||
|
@ -210,28 +214,31 @@
|
||||||
[stretch-matrix stretch-matrix-inverse]))
|
[stretch-matrix stretch-matrix-inverse]))
|
||||||
|
|
||||||
|
|
||||||
(defn set-points-path
|
(defn apply-transform-path
|
||||||
[shape points]
|
[shape transform]
|
||||||
(let [shape (reduce (fn [acc [idx {:keys [x y]}]]
|
(let [content (gpa/transform-content (:content shape) transform)
|
||||||
(-> acc
|
points (gpa/content->points content)
|
||||||
(assoc-in [:content idx :params :x] x)
|
rotation (mod (+ (:rotation shape 0)
|
||||||
(assoc-in [:content idx :params :y] y))) shape (d/enumerate points))
|
(or (get-in shape [:modifiers :rotation]) 0))
|
||||||
|
360)
|
||||||
shape (assoc shape
|
selrect (gpa/content->selrect content)]
|
||||||
|
(assoc shape
|
||||||
|
:content content
|
||||||
:points points
|
:points points
|
||||||
:selrect (gpr/points->selrect points))]
|
:selrect selrect
|
||||||
shape))
|
:rotation rotation)))
|
||||||
|
|
||||||
(defn set-points-curve
|
(defn apply-transform-curve
|
||||||
[shape points]
|
[shape transform]
|
||||||
shape)
|
shape)
|
||||||
|
|
||||||
(defn set-points-rect
|
(defn apply-transform-rect
|
||||||
"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 points]
|
[shape transform]
|
||||||
;;
|
;;
|
||||||
(let [center (gco/center-points points)
|
(let [points (-> shape :points (transform-points transform))
|
||||||
|
center (gco/center-points points)
|
||||||
|
|
||||||
;; Reverse the current transformation stack to get the base rectangle
|
;; Reverse the current transformation stack to get the base rectangle
|
||||||
tr-inverse (:transform-inverse shape (gmt/matrix))
|
tr-inverse (:transform-inverse shape (gmt/matrix))
|
||||||
|
@ -259,18 +266,18 @@
|
||||||
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
|
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
|
||||||
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
|
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
|
||||||
(assoc $ :points (into [] points))
|
(assoc $ :points (into [] points))
|
||||||
(assoc $ :selrect (gpr/rect->selrect rect-shape) #_(gpr/points->selrect points))
|
(assoc $ :selrect (gpr/rect->selrect rect-shape))
|
||||||
(update $ :rotation #(mod (+ (or % 0)
|
(update $ :rotation #(mod (+ (or % 0)
|
||||||
(or (get-in $ [:modifiers :rotation]) 0)) 360)))]
|
(or (get-in $ [:modifiers :rotation]) 0)) 360)))]
|
||||||
new-shape))
|
new-shape))
|
||||||
|
|
||||||
(defn set-points [shape points]
|
(defn apply-transform [shape transform]
|
||||||
(let [set-points-fn
|
(let [apply-transform-fn
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:path set-points-path
|
:path apply-transform-path
|
||||||
:curve set-points-curve
|
:curve apply-transform-curve
|
||||||
set-points-rect)]
|
apply-transform-rect)]
|
||||||
(set-points-fn shape points)))
|
(apply-transform-fn shape transform)))
|
||||||
|
|
||||||
(defn set-flip [shape modifiers]
|
(defn set-flip [shape modifiers]
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
|
@ -279,13 +286,11 @@
|
||||||
|
|
||||||
(defn transform-shape [shape]
|
(defn transform-shape [shape]
|
||||||
(if (:modifiers shape)
|
(if (:modifiers shape)
|
||||||
(let [points (:points shape (shape->points shape))
|
(let [center (gco/center-shape shape)
|
||||||
center (gco/center-points points)
|
transform (modifiers->transform (:transform shape (gmt/matrix)) center (:modifiers shape))]
|
||||||
transform (modifiers->transform (:transform shape (gmt/matrix)) center (:modifiers shape))
|
|
||||||
tr-points (transform-points points transform)]
|
|
||||||
(-> shape
|
(-> shape
|
||||||
(set-flip (:modifiers shape))
|
(set-flip (:modifiers shape))
|
||||||
(set-points tr-points)
|
(apply-transform transform)
|
||||||
(dissoc :modifiers)))
|
(dissoc :modifiers)))
|
||||||
shape))
|
shape))
|
||||||
|
|
||||||
|
|
|
@ -135,3 +135,6 @@
|
||||||
(if (< num from)
|
(if (< num from)
|
||||||
from
|
from
|
||||||
(if (> num to) to num)))
|
(if (> num to) to num)))
|
||||||
|
|
||||||
|
(defn almost-zero? [num]
|
||||||
|
(< (abs num) 1e-8))
|
||||||
|
|
|
@ -50,14 +50,9 @@
|
||||||
|
|
||||||
|
|
||||||
(defn calculate-selrect [shape]
|
(defn calculate-selrect [shape]
|
||||||
(let [points (->> shape
|
|
||||||
:content
|
|
||||||
(mapv #(gpt/point
|
|
||||||
(-> % :params :x)
|
|
||||||
(-> % :params :y))))]
|
|
||||||
(assoc shape
|
(assoc shape
|
||||||
:points points
|
:points (gsh/content->points (:content shape))
|
||||||
:selrect (gsh/points->selrect points))))
|
:selrect (gsh/content->selrect (:content shape))))
|
||||||
|
|
||||||
(defn init-path []
|
(defn init-path []
|
||||||
(ptk/reify ::init-path
|
(ptk/reify ::init-path
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
[app.main.ui.shapes.filters :as filters]
|
[app.main.ui.shapes.filters :as filters]
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.workspace.shapes.common :as common]
|
[app.main.ui.workspace.shapes.common :as common]
|
||||||
[app.util.geom.path :as ugp]))
|
[app.util.geom.path :as ugp]
|
||||||
|
[app.common.geom.shapes.path :as gsp]))
|
||||||
|
|
||||||
(mf/defc path-wrapper
|
(mf/defc path-wrapper
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
|
@ -53,7 +54,6 @@
|
||||||
:on-double-click on-double-click
|
:on-double-click on-double-click
|
||||||
:on-mouse-down on-mouse-down
|
:on-mouse-down on-mouse-down
|
||||||
:on-context-menu on-context-menu}
|
:on-context-menu on-context-menu}
|
||||||
|
|
||||||
[:& path/path-shape {:shape shape
|
[:& path/path-shape {:shape shape
|
||||||
:background? true}]]))
|
:background? true}]]))
|
||||||
|
|
||||||
|
|
|
@ -204,11 +204,14 @@
|
||||||
picking-color?]} local
|
picking-color?]} local
|
||||||
|
|
||||||
page-id (mf/use-ctx ctx/current-page-id)
|
page-id (mf/use-ctx ctx/current-page-id)
|
||||||
selrect-orig (->> (mf/deref refs/selected-objects)
|
|
||||||
|
selected-objects (mf/deref refs/selected-objects)
|
||||||
|
selrect-orig (->> selected-objects
|
||||||
|
(gsh/selection-rect))
|
||||||
|
selrect (->> selected-objects
|
||||||
|
(map #(assoc % :modifiers (:modifiers local)))
|
||||||
|
(map gsh/transform-shape)
|
||||||
(gsh/selection-rect))
|
(gsh/selection-rect))
|
||||||
selrect (-> selrect-orig
|
|
||||||
(assoc :modifiers (:modifiers local))
|
|
||||||
(gsh/transform-shape))
|
|
||||||
|
|
||||||
alt? (mf/use-state false)
|
alt? (mf/use-state false)
|
||||||
viewport-ref (mf/use-ref nil)
|
viewport-ref (mf/use-ref nil)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue