Use new defrecord for geom data structures

This commit is contained in:
Andrey Antukh
2023-06-20 14:03:35 +02:00
parent 3f14308908
commit ea5b153578
3 changed files with 248 additions and 160 deletions

View File

@@ -13,6 +13,7 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.openapi :as-alias oapi]
@@ -27,21 +28,21 @@
(def precision 6)
;; --- Matrix Impl
(defrecord Matrix [^double a
^double b
^double c
^double d
^double e
^double f]
(cr/defrecord Matrix [^double a
^double b
^double c
^double d
^double e
^double f]
Object
(toString [_]
(toString [this]
(dm/fmt "matrix(%, %, %, %, %, %)"
(mth/to-fixed a precision)
(mth/to-fixed b precision)
(mth/to-fixed c precision)
(mth/to-fixed d precision)
(mth/to-fixed e precision)
(mth/to-fixed f precision))))
(mth/to-fixed (.-a this) precision)
(mth/to-fixed (.-b this) precision)
(mth/to-fixed (.-c this) precision)
(mth/to-fixed (.-d this) precision)
(mth/to-fixed (.-e this) precision)
(mth/to-fixed (.-f this) precision))))
(defn matrix?
"Return true if `v` is Matrix instance."
@@ -51,9 +52,9 @@
(defn matrix
"Create a new matrix instance."
([]
(Matrix. 1 0 0 1 0 0))
(pos->Matrix 1 0 0 1 0 0))
([a b c d e f]
(Matrix. a b c d e f)))
(pos->Matrix a b c d e f)))
(def number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?")
@@ -100,7 +101,7 @@
(sg/small-double)
(sg/small-double)
(sg/small-double) )
(sg/fmap #(apply ->Matrix %)))
(sg/fmap #(apply pos->Matrix %)))
::oapi/type "string"
::oapi/format "matrix"
::oapi/decode decode
@@ -120,24 +121,54 @@
(s/def ::matrix
(s/and ::matrix-attrs matrix?))
(defn close?
[^Matrix m1 ^Matrix m2]
(and (mth/close? (.-a m1) (.-a m2))
(mth/close? (.-b m1) (.-b m2))
(mth/close? (.-c m1) (.-c m2))
(mth/close? (.-d m1) (.-d m2))
(mth/close? (.-e m1) (.-e m2))
(mth/close? (.-f m1) (.-f m2))))
(and ^boolean (mth/close? (.-a m1) (.-a m2))
^boolean (mth/close? (.-b m1) (.-b m2))
^boolean (mth/close? (.-c m1) (.-c m2))
^boolean (mth/close? (.-d m1) (.-d m2))
^boolean (mth/close? (.-e m1) (.-e m2))
^boolean (mth/close? (.-f m1) (.-f m2))))
(defn unit? [^Matrix m1]
(and (some? m1)
(mth/close? (.-a m1) 1)
(mth/close? (.-b m1) 0)
(mth/close? (.-c m1) 0)
(mth/close? (.-d m1) 1)
(mth/close? (.-e m1) 0)
(mth/close? (.-f m1) 0)))
(and ^boolean (some? m1)
^boolean (mth/close? (.-a m1) 1)
^boolean (mth/close? (.-b m1) 0)
^boolean (mth/close? (.-c m1) 0)
^boolean (mth/close? (.-d m1) 1)
^boolean (mth/close? (.-e m1) 0)
^boolean (mth/close? (.-f m1) 0)))
(defn multiply!
[^Matrix m1 ^Matrix m2]
(let [m1a (.-a m1)
m1b (.-b m1)
m1c (.-c m1)
m1d (.-d m1)
m1e (.-e m1)
m1f (.-f m1)
m2a (.-a m2)
m2b (.-b m2)
m2c (.-c m2)
m2d (.-d m2)
m2e (.-e m2)
m2f (.-f m2)]
#?@(:cljs
[(set! (.-a m1) (+ (* m1a m2a) (* m1c m2b)))
(set! (.-b m1) (+ (* m1b m2a) (* m1d m2b)))
(set! (.-c m1) (+ (* m1a m2c) (* m1c m2d)))
(set! (.-d m1) (+ (* m1b m2c) (* m1d m2d)))
(set! (.-e m1) (+ (* m1a m2e) (* m1c m2f) m1e))
(set! (.-f m1) (+ (* m1b m2e) (* m1d m2f) m1f))
m1]
:clj
[(pos->Matrix
(+ (* m1a m2a) (* m1c m2b))
(+ (* m1b m2a) (* m1d m2b))
(+ (* m1a m2c) (* m1c m2d))
(+ (* m1b m2c) (* m1d m2d))
(+ (* m1a m2e) (* m1c m2f) m1e)
(+ (* m1b m2e) (* m1d m2f) m1f))])))
(defn multiply
([^Matrix m1 ^Matrix m2]
@@ -162,7 +193,7 @@
m2e (.-e m2)
m2f (.-f m2)]
(Matrix.
(pos->Matrix
(+ (* m1a m2a) (* m1c m2b))
(+ (* m1b m2a) (* m1d m2b))
(+ (* m1a m2c) (* m1c m2d))
@@ -171,51 +202,28 @@
(+ (* m1b m2e) (* m1d m2f) m1f)))))
([m1 m2 & others]
(reduce multiply (multiply m1 m2) others)))
(defn multiply!
[^Matrix m1 ^Matrix m2]
(let [m1a (.-a m1)
m1b (.-b m1)
m1c (.-c m1)
m1d (.-d m1)
m1e (.-e m1)
m1f (.-f m1)
m2a (.-a m2)
m2b (.-b m2)
m2c (.-c m2)
m2d (.-d m2)
m2e (.-e m2)
m2f (.-f m2)]
#?@(:cljs [(set! (.-a m1) (+ (* m1a m2a) (* m1c m2b)))
(set! (.-b m1) (+ (* m1b m2a) (* m1d m2b)))
(set! (.-c m1) (+ (* m1a m2c) (* m1c m2d)))
(set! (.-d m1) (+ (* m1b m2c) (* m1d m2d)))
(set! (.-e m1) (+ (* m1a m2e) (* m1c m2f) m1e))
(set! (.-f m1) (+ (* m1b m2e) (* m1d m2f) m1f))
m1]
:clj [(Matrix.
(+ (* m1a m2a) (* m1c m2b))
(+ (* m1b m2a) (* m1d m2b))
(+ (* m1a m2c) (* m1c m2d))
(+ (* m1b m2c) (* m1d m2d))
(+ (* m1a m2e) (* m1c m2f) m1e)
(+ (* m1b m2e) (* m1d m2f) m1f))])))
(reduce multiply! (multiply m1 m2) others)))
(defn add-translate
"Given two TRANSLATE matrixes (only e and f have significative
values), combine them. Quicker than multiplying them, for this
precise case."
([{m1e :e m1f :f} {m2e :e m2f :f}]
(Matrix. 1 0 0 1 (+ m1e m2e) (+ m1f m2f)))
([^Matrix m1 ^Matrix m2]
(let [m1e (dm/get-prop m1 :e)
m1f (dm/get-prop m1 :f)
m2e (dm/get-prop m2 :e)
m2f (dm/get-prop m2 :f)]
(pos->Matrix 1 0 0 1 (+ m1e m2e) (+ m1f m2f))))
([m1 m2 & others]
(reduce add-translate (add-translate m1 m2) others)))
;; FIXME: optimize?
(defn substract
[{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f}
{m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}]
(Matrix.
(pos->Matrix
(- m1a m2a) (- m1b m2b) (- m1c m2c)
(- m1d m2d) (- m1e m2e) (- m1f m2f)))
@@ -227,13 +235,24 @@
(defn translate-matrix
([pt]
(assert (gpt/point? pt))
(Matrix. 1 0 0 1
(dm/get-prop pt :x)
(dm/get-prop pt :y)))
(dm/assert! (gpt/point? pt))
(pos->Matrix 1 0 0 1
(dm/get-prop pt :x)
(dm/get-prop pt :y)))
([x y]
(Matrix. 1 0 0 1 x y)))
(pos->Matrix 1 0 0 1 x y)))
(defn translate-matrix-neg
([pt]
(dm/assert! (gpt/point? pt))
(pos->Matrix 1 0 0 1
(- (dm/get-prop pt :x))
(- (dm/get-prop pt :y))))
([x y]
(pos->Matrix 1 0 0 1 (- x) (- y))))
(defn scale-matrix
([pt center]
@@ -241,10 +260,10 @@
sy (dm/get-prop pt :y)
cx (dm/get-prop center :x)
cy (dm/get-prop center :y)]
(Matrix. sx 0 0 sy (- cx (* cx sx)) (- cy (* cy sy)))))
(pos->Matrix sx 0 0 sy (- cx (* cx sx)) (- cy (* cy sy)))))
([pt]
(assert (gpt/point? pt))
(Matrix. (dm/get-prop pt :x) 0 0 (dm/get-prop pt :y) 0 0)))
(dm/assert! (gpt/point? pt))
(pos->Matrix (dm/get-prop pt :x) 0 0 (dm/get-prop pt :y) 0 0)))
(defn rotate-matrix
([angle point]
@@ -258,15 +277,15 @@
ns (- s)
tx (+ (* c nx) (* ns ny) cx)
ty (+ (* s nx) (* c ny) cy)]
(Matrix. c s ns c tx ty)))
(pos->Matrix c s ns c tx ty)))
([angle]
(let [a (mth/radians angle)]
(Matrix. (mth/cos a)
(mth/sin a)
(- (mth/sin a))
(mth/cos a)
0
0))))
(pos->Matrix (mth/cos a)
(mth/sin a)
(- (mth/sin a))
(mth/cos a)
0
0))))
(defn skew-matrix
([angle-x angle-y point]
@@ -276,7 +295,7 @@
([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))))
(pos->Matrix 1 m2 m1 1 0 0))))
(defn rotate
"Apply rotation transformation to the matrix."
@@ -337,6 +356,7 @@
(translate (gpt/negate pt)))
mtx))
;; FIXME: performance
(defn determinant
"Determinant for the affinity transform"
[{:keys [a b c d _ _]}]
@@ -346,14 +366,14 @@
"Gets the inverse of the affinity transform `mtx`"
[{:keys [a b c d e f] :as mtx}]
(let [det (determinant mtx)]
(when-not (mth/almost-zero? det)
(when-not ^boolean (mth/almost-zero? det)
(let [a' (/ d det)
b' (/ (- b) det)
c' (/ (- c) det)
d' (/ a det)
e' (/ (- (* c f) (* d e)) det)
f' (/ (- (* b e) (* a f)) det)]
(Matrix. a' b' c' d' e' f')))))
(pos->Matrix a' b' c' d' e' f')))))
(defn round
[mtx]
@@ -377,11 +397,11 @@
point))
(defn move?
[{:keys [a b c d _ _]}]
(and (mth/almost-zero? (- a 1))
(mth/almost-zero? b)
(mth/almost-zero? c)
(mth/almost-zero? (- d 1))))
[m]
(and ^boolean (mth/almost-zero? (- (dm/get-prop m :a) 1))
^boolean (mth/almost-zero? (dm/get-prop m :b))
^boolean (mth/almost-zero? (dm/get-prop m :c))
^boolean (mth/almost-zero? (- (dm/get-prop m :d) 1))))
#?(:clj
(fres/add-handlers!

View File

@@ -16,6 +16,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.math :as mth]
[app.common.record :as cr]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.openapi :as-alias oapi]
@@ -29,7 +30,7 @@
;; --- Point Impl
(defrecord Point [x y])
(cr/defrecord Point [x y])
(defn s
[pt]
@@ -62,7 +63,7 @@
(map->Point p)
(if (string? p)
(let [[x y] (->> (str/split p #",") (mapv parse-double))]
(Point. x y))
(pos->Point x y))
p)))
(encode [p]
@@ -76,7 +77,7 @@
:description "Point"
:error/message "expected a valid point"
:gen/gen (->> (sg/tuple (sg/small-int) (sg/small-int))
(sg/fmap #(apply ->Point %)))
(sg/fmap #(apply pos->Point %)))
::oapi/type "string"
::oapi/format "point"
::oapi/decode decode
@@ -90,7 +91,7 @@
(defn point
"Create a Point instance."
([] (Point. 0 0))
([] (pos->Point 0 0))
([v]
(cond
(point? v)
@@ -100,12 +101,12 @@
(point v v)
(point-like? v)
(Point. (:x v) (:y v))
(pos->Point (:x v) (:y v))
:else
(ex/raise :hint "invalid arguments (on pointer constructor)" :value v)))
([x y]
(Point. x y)))
(pos->Point x y)))
(defn close?
[p1 p2]
@@ -129,22 +130,24 @@
(and (point? p1)
(point? p2)))
(Point. (+ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(+ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(pos->Point (+ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(+ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn subtract
"Returns the subtraction of the supplied value to both
coordinates of the point as a new point."
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (- (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(dm/assert!
"arguments should be pointer instance"
(and (point? p1)
(point? p2)))
(pos->Point (- (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn multiply
"Returns the subtraction of the supplied value to both
@@ -153,20 +156,20 @@
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (* (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(* (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(pos->Point (* (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(* (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn divide
[p1 p2]
(assert (and (point? p1)
(point? p2))
"arguments should be pointer instance")
(Point. (/ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(/ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(pos->Point (/ (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(/ (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))
(defn min
([] nil)
@@ -175,10 +178,10 @@
(cond
(nil? p1) p2
(nil? p2) p1
:else (Point. (c/min (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/min (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
:else (pos->Point (c/min (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/min (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
(defn max
([] nil)
([p1] p1)
@@ -186,21 +189,21 @@
(cond
(nil? p1) p2
(nil? p2) p1
:else (Point. (c/max (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/max (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
:else (pos->Point (c/max (dm/get-prop p1 :x)
(dm/get-prop p2 :x))
(c/max (dm/get-prop p1 :y)
(dm/get-prop p2 :y))))))
(defn inverse
[pt]
(assert (point? pt) "point instance expected")
(Point. (/ 1.0 (dm/get-prop pt :x))
(/ 1.0 (dm/get-prop pt :y))))
(pos->Point (/ 1.0 (dm/get-prop pt :x))
(/ 1.0 (dm/get-prop pt :y))))
(defn negate
[pt]
(assert (point? pt) "point instance expected")
(Point. (- (dm/get-prop pt :x))
(- (dm/get-prop pt :y))))
(pos->Point (- (dm/get-prop pt :x))
(- (dm/get-prop pt :y))))
(defn distance
"Calculate the distance between two points."
@@ -224,8 +227,8 @@
(dm/get-prop p2 :x))
dy (- (dm/get-prop p1 :y)
(dm/get-prop p2 :y))]
(Point. (mth/abs dx)
(mth/abs dy))))
(pos->Point (mth/abs dx)
(mth/abs dy))))
(defn length
[pt]
@@ -292,8 +295,8 @@
(assert (number? angle) "expected number")
(let [len (length p)
angle (mth/radians angle)]
(Point. (* (mth/cos angle) len)
(* (mth/sin angle) len))))
(pos->Point (* (mth/cos angle) len)
(* (mth/sin angle) len))))
(defn quadrant
"Return the quadrant of the angle of the point."
@@ -313,22 +316,21 @@
([pt decimals]
(assert (point? pt) "expected point instance")
(assert (number? decimals) "expected number instance")
(Point. (mth/precision (dm/get-prop pt :x) decimals)
(mth/precision (dm/get-prop pt :y) decimals))))
(pos->Point (mth/precision (dm/get-prop pt :x) decimals)
(mth/precision (dm/get-prop pt :y) decimals))))
(defn round-step
"Round the coordinates to the closest half-point"
[pt step]
(assert (point? pt) "expected point instance")
(Point. (mth/round (dm/get-prop pt :x) step)
(mth/round (dm/get-prop pt :y) step)))
(pos->Point (mth/round (dm/get-prop pt :x) step)
(mth/round (dm/get-prop pt :y) step)))
(defn transform
"Transform a point applying a matrix transformation."
[p m]
(when (point? p)
(if (nil? m)
p
(if (some? m)
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)
a (dm/get-prop m :a)
@@ -337,18 +339,51 @@
d (dm/get-prop m :d)
e (dm/get-prop m :e)
f (dm/get-prop m :f)]
(Point. (+ (* x a) (* y c) e)
(+ (* x b) (* y d) f))))))
(pos->Point (+ (* x a) (* y c) e)
(+ (* x b) (* y d) f)))
p)))
(defn transform!
[p m]
(dm/assert!
"expected valid rect and matrix instances"
(and (some? p) (some? m)))
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)
a (dm/get-prop m :a)
b (dm/get-prop m :b)
c (dm/get-prop m :c)
d (dm/get-prop m :d)
e (dm/get-prop m :e)
f (dm/get-prop m :f)]
#?(:clj
(pos->Point (+ (* x a) (* y c) e)
(+ (* x b) (* y d) f))
:cljs
(do
(set! (.-x p) (+ (* x a) (* y c) e))
(set! (.-y p) (+ (* x b) (* y d) f))
p))))
(defn matrix->point
"Returns a result of transform an identity point with the provided
matrix instance"
[m]
(let [e (dm/get-prop m :e)
f (dm/get-prop m :f)]
(pos->Point e f)))
;; Vector functions
(defn to-vec [p1 p2]
(subtract p2 p1))
(defn scale
[p scalar]
(Point. (* (dm/get-prop p :x) scalar)
(* (dm/get-prop p :y) scalar)))
(pos->Point (* (dm/get-prop p :x) scalar)
(* (dm/get-prop p :y) scalar)))
(defn dot
[p1 p2]
@@ -361,14 +396,14 @@
[p1]
(let [p-length (length p1)]
(if (mth/almost-zero? p-length)
(Point. 0 0)
(Point. (/ (dm/get-prop p1 :x) p-length)
(/ (dm/get-prop p1 :y) p-length)))))
(pos->Point 0 0)
(pos->Point (/ (dm/get-prop p1 :x) p-length)
(/ (dm/get-prop p1 :y) p-length)))))
(defn perpendicular
[pt]
(Point. (- (dm/get-prop pt :y))
(dm/get-prop pt :x)))
(pos->Point (- (dm/get-prop pt :y))
(dm/get-prop pt :x)))
(defn project
"V1 perpendicular projection on vector V2"
@@ -419,7 +454,7 @@
[p1 p2 t]
(let [x (mth/lerp (dm/get-prop p1 :x) (dm/get-prop p2 :x) t)
y (mth/lerp (dm/get-prop p1 :y) (dm/get-prop p2 :y) t)]
(Point. x y)))
(pos->Point x y)))
(defn rotate
"Rotates the point around center with an angle"
@@ -441,7 +476,7 @@
y (+ (* sa (- px cx))
(* ca (- py cy))
cy)]
(Point. x y)))
(pos->Point x y)))
(defn scale-from
"Moves a point in the vector that creates with center with a scale
@@ -457,10 +492,10 @@
[p]
(let [x (dm/get-prop p :x)
y (dm/get-prop p :y)]
(Point. (if (mth/almost-zero? x) 0.001 x)
(if (mth/almost-zero? y) 0.001 y))))
(pos->Point (if (mth/almost-zero? x) 0.001 x)
(if (mth/almost-zero? y) 0.001 y))))
;; FIXME: perfromance
(defn abs
[point]
(-> point

View File

@@ -11,9 +11,10 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.record :as rc]
[app.common.transit :as t]))
(defrecord Rect [x y width height x1 y1 x2 y2])
(rc/defrecord Rect [x y width height x1 y1 x2 y2])
(defn rect?
[o]
@@ -62,10 +63,7 @@
(when (d/num? x y width height)
(let [w (mth/max width 0.01)
h (mth/max height 0.01)]
(->Rect x y w h x y (+ x w) (+ y h)))))
([x y w h x1 y1 x2 y2]
(->Rect x y w h x1 y1 x2 y2)))
(pos->Rect x y w h x y (+ x w) (+ y h))))))
(def empty-rect
(make-rect 0 0 0.01 0.01))
@@ -104,6 +102,31 @@
:x2 (+ x w)
:y2 (+ y h)))))
(defn update-rect!
[rect type]
(case type
(:size :position)
(let [x (dm/get-prop rect :x)
y (dm/get-prop rect :y)
w (dm/get-prop rect :width)
h (dm/get-prop rect :height)]
(rc/assoc! rect
:x1 x
:y1 y
:x2 (+ x w)
:y2 (+ y h)))
:corners
(let [x1 (dm/get-prop rect :x1)
y1 (dm/get-prop rect :y1)
x2 (dm/get-prop rect :x2)
y2 (dm/get-prop rect :y2)]
(rc/assoc! rect
:x (mth/min x1 x2)
:y (mth/min y1 y2)
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))))
(defn close-rect?
[rect1 rect2]
@@ -123,7 +146,6 @@
(defn rect->points
[rect]
(dm/assert!
"expected rect instance"
(rect? rect))
@@ -140,6 +162,12 @@
(gpt/point (+ x w) (+ y h))
(gpt/point x (+ y h))]))))
(defn rect->point
"Extract the position part of the rect"
[rect]
(gpt/point (dm/get-prop rect :x)
(dm/get-prop rect :y)))
(defn rect->center
[rect]
(dm/assert! (rect? rect))
@@ -231,17 +259,22 @@
(when (d/num? minx miny maxx maxy)
(make-rect minx miny (- maxx minx) (- maxy miny))))))
(defn center->rect [{:keys [x y]} width height]
(when (d/num? x y width height)
(make-rect (- x (/ width 2))
(- y (/ height 2))
width
height)))
(defn center->rect
[point w h]
(when (some? point)
(let [x (dm/get-prop point :x)
y (dm/get-prop point :y)]
(when (d/num? x y w h)
(make-rect (- x (/ w 2))
(- y (/ h 2))
w
h)))))
(defn s=
[a b]
(mth/almost-zero? (- a b)))
;; FIXME: performance
(defn overlaps-rects?
"Check for two rects to overlap. Rects won't overlap only if
one of them is fully to the left or the top"