0 | module Geom.Point
  1 |
  2 | import Derive.Prelude
  3 | import Data.Refined
  4 | import Geom.Angle
  5 | import Geom.Matrix
  6 | import Geom.Scale
  7 | import Geom.Vector
  8 |
  9 | %default total
 10 | %language ElabReflection
 11 |
 12 | --------------------------------------------------------------------------------
 13 | --          Affine Transformations
 14 | --------------------------------------------------------------------------------
 15 |
 16 | ||| An affine transformation is a linear transformation (currently, a rotation
 17 | ||| or scaling) followed by a translation in the new coordinate system.
 18 | |||
 19 | ||| We use affine transformations to keep track of the coordinate system
 20 | ||| we are currently in. In the UI, if a user zooms in or moves the canvas,
 21 | ||| the coordinates we get from the UI must be adjusted by reversing these
 22 | ||| transformations in order to get the correct canvas coordinates. These
 23 | ||| need then again be adjusted (by a scaling factor) to get the coordinates
 24 | ||| of the molecule.
 25 | |||
 26 | ||| In order to make sure these transformations and adjustments are not
 27 | ||| forgotten, we index all `Point`s we work with the the affine transformation
 28 | ||| of the coordinate system they come from. Via function `convert`, points
 29 | ||| from one coordinate system can be converted to the corresponding points in
 30 | ||| another one.
 31 | public export
 32 | record AffineTransformation where
 33 |   constructor AT
 34 |   transform : LinearTransformation
 35 |   translate : Vector Id
 36 |
 37 | ||| The identity transformation
 38 | public export
 39 | Id : AffineTransformation
 40 | Id = AT Id vzero
 41 |
 42 | ||| An affine transformation consisting only of a scaling by the given factor.
 43 | export
 44 | scaling : Scale -> AffineTransformation
 45 | scaling sc = AT (scaling sc) vzero
 46 |
 47 | export
 48 | Semigroup AffineTransformation where
 49 |   AT tl vl <+> AT tr vr =
 50 |     let vr2 := transform tl vr in AT (tl <+> tr) (vr2 + vl)
 51 |
 52 | export
 53 | Monoid AffineTransformation where neutral = Id
 54 |
 55 | ||| Computes the inverse of an affine transformation so that
 56 | ||| `t <+> inverse t` is the identity (`Id`; modulo rounding errors)
 57 | export
 58 | inverse : AffineTransformation -> AffineTransformation
 59 | inverse (AT tr v) = let i := inverse tr in  AT i $ negate (transform i v)
 60 |
 61 | namespace Affine
 62 |
 63 |   ||| Creates an affine transformation corresponding to a translation
 64 |   ||| by the given vector.
 65 |   export %inline
 66 |   translate : Vector Id -> AffineTransformation
 67 |   translate v = AT Id v
 68 |
 69 | --------------------------------------------------------------------------------
 70 | --          Point
 71 | --------------------------------------------------------------------------------
 72 |
 73 | ||| A point in an affine space such as the user interface or the
 74 | ||| coordinate system of the molecule.
 75 | |||
 76 | ||| Unlike `Vector`, a `Point` is somewhat of an abstract entity.
 77 | ||| We can compute a vector for connecting two points, but we cannot
 78 | ||| add two points together (that does not make sense). Likewise, the
 79 | ||| difference between two points results in the vector connecting
 80 | ||| the two.
 81 | |||
 82 | ||| A point is indexed by the `AffineTransformation` corresponding to its
 83 | ||| coordinate system. See @AffineTransformation for some more details.
 84 | public export
 85 | record Point (t : AffineTransformation) where
 86 |   constructor P
 87 |   x : Double
 88 |   y : Double
 89 |
 90 | %runElab deriveIndexed "Point" [Show,Eq,Ord]
 91 |
 92 | ||| The origin at `(0,0)`.
 93 | export
 94 | origin : Point t
 95 | origin = P 0 0
 96 |
 97 | ||| The difference between two points is the vector connecting
 98 | ||| the points.
 99 | export
100 | (-) : Point t -> Point t -> Vector t.transform
101 | (-) (P x1 y1) (P x2 y2) = V (x1 - x2) (y1 - y2)
102 |
103 | ||| Convert a point from one affine space to its image in another
104 | ||| affine space.
105 | export
106 | convert : {s,t : _} -> Point s -> Point t
107 | convert p =
108 |   let AT l (V dx dy) := t <+> inverse s
109 |       V x2 y2 := transform l (p - origin)
110 |    in P (x2 + dx) (y2 + dy)
111 |
112 | export %inline
113 | {s,t : _} -> Cast (Point s) (Point t) where cast = convert
114 |
115 | --------------------------------------------------------------------------------
116 | --          Interface
117 | --------------------------------------------------------------------------------
118 |
119 | ||| Interface for converting a value to a point in the affine space
120 | ||| represented by transformation `t`.
121 | public export
122 | interface GetPoint (0 a : Type) where
123 |   gtrans : AffineTransformation
124 |   point  : a -> Point gtrans
125 |
126 | public export
127 | 0 GPoint : (a : Type) -> (g : GetPoint a) => Type
128 | GPoint a = Point (gtrans @{g})
129 |
130 | public export
131 | 0 GVect : (a : Type) -> (g : GetPoint a) => Type
132 | GVect a = Vector (transform $ gtrans @{g})
133 |
134 | public export
135 | {t : _} -> GetPoint (Point t) where
136 |   gtrans = t
137 |   point  = id
138 |
139 | ||| Interface for modification of a value `a` by modifying its points in the
140 | ||| affine space.
141 | public export
142 | interface ModPoint (0 a : Type) where
143 |   mtrans   : AffineTransformation
144 |   modPoint : (Point mtrans -> Point mtrans) -> a -> a
145 |
146 | public export
147 | {t : _} -> ModPoint (Point t) where
148 |   mtrans       = t
149 |   modPoint f p = f p
150 |
151 | public export
152 | 0 MPoint : (a : Type) -> (m : ModPoint a) => Type
153 | MPoint a = Point (mtrans @{m})
154 |
155 | public export
156 | 0 MVect : (a : Type) -> (m : ModPoint a) => Type
157 | MVect a = Vector (transform $ mtrans @{m})
158 |
159 | ||| Places an object at a new point in space.
160 | export %inline
161 | setPoint : ModPoint a => MPoint a -> a -> a
162 | setPoint = modPoint . const
163 |
164 | ||| Return the coordinates of a point in the reference affine space.
165 | export %inline
166 | pointId : GetPoint a => a -> Point Id
167 | pointId = convert . point
168 |
169 | ||| Translate an object in 2D space by the given vector.
170 | export
171 | translate : ModPoint a => MVect a -> a -> a
172 | translate (V dx dy) = modPoint (\(P x y) => P (x + dx) (y + dy))
173 |
174 | ||| Scale an object in 2D space by the given factor.
175 | export
176 | scale : ModPoint a => Scale -> a -> a
177 | scale v = modPoint (\(P x y) => P (x * v.value) (y * v.value))
178 |
179 | ||| Rotates an object by the given angle around the given poin
180 | export
181 | rotateAt : ModPoint a => MPoint a -> Angle -> a -> a
182 | rotateAt o a = modPoint $ \p => let v := p-o in translate (rotate a v - v) p
183 |
184 | ||| Rotates an object by the given angle around the origin
185 | export %inline
186 | rotate : ModPoint a => Angle -> a -> a
187 | rotate = rotateAt origin
188 |
189 | ||| Calculate the distance between two objects.
190 | export %inline
191 | distance : GetPoint a => a -> a -> Double
192 | distance x y = length (point x - point y)
193 |
194 | ||| Calculate the distance of an object from zero.
195 | export %inline
196 | distanceFromZero : GetPoint a => a -> Double
197 | distanceFromZero = distance origin . point
198 |
199 | ||| Checks, if two objects are no further apart than `delta`.
200 | export %inline
201 | near : GetPoint a => (x,y : a) -> (delta : Double) -> Bool
202 | near x y = (distance x y <=)
203 |
204 | record Center2d where
205 |   constructor C2D
206 |   sx    : Double
207 |   sy    : Double
208 |   count : Nat
209 |
210 | toCenter : Center2d -> Point t
211 | toCenter (C2D _ _ 0) = P 0 0
212 | toCenter (C2D x y n) = let dn := cast n in P (x/dn) (y/dn)
213 |
214 | addPoint : GetPoint a => Center2d -> a -> Center2d
215 | addPoint (C2D sx sy c) v = let P x y := point v in C2D (sx+x) (sy+y) (S c)
216 |
217 | ||| Computes the center of mass of a set of points.
218 | export
219 | center2d : GetPoint a => Foldable t => t a -> GPoint a
220 | center2d = toCenter . foldl addPoint (C2D 0 0 0)
221 |
222 | --------------------------------------------------------------------------------
223 | --          Utilities
224 | --------------------------------------------------------------------------------
225 |
226 | ||| Transform a point by applying a transformation patrix.
227 | export
228 | apply : Matrix -> Point t -> Point t
229 | apply (M a b c d) (P x y) = P (a * x + b * y) (c * x + d * y)
230 |
231 | ||| Calculates a perpendicual point to the line from the first line point
232 | ||| with a certain distance. The `positive` flag is to choose the direction
233 | ||| from the line (positive -> right top, negative -> left bottom).
234 | export
235 | perpendicularPoint :
236 |      {k : _}
237 |   -> (p1,p2 : Point k)
238 |   -> (distance : Double)
239 |   -> (positive : Bool)
240 |   -> Point k
241 | perpendicularPoint p1 p2 d flag =
242 |   let v   := scaleTo d $ p2 - p1
243 |       phi := if flag then halfPi else negate halfPi
244 |    in translate (rotate phi v) p1
245 |
246 | ||| Tries to compute the intersection of two lines
247 | ||| from points p11 to p12 and p21 to p22.
248 | |||
249 | ||| This tries to solve the following system of equations:
250 | |||
251 | |||   (I)  : `x11 + r * (x12 - x11) = x21 + s * (x22 - x21)`
252 | |||   (II) : `y11 + r * (y12 - y11) = y21 + s * (y22 - y21)`
253 | export
254 | intersect : (p11,p12,p21,p22 : Point t) -> Maybe (Point t)
255 | intersect (P x11 y11) (P x12 y12) (P x21 y21) (P x22 y22) =
256 |   let p       := P {t} (x21 - x11) (y21 - y11)
257 |       m       := M (x12 - x11) (x21 - x22) (y12 - y11) (y21 - y22)
258 |       Just m' := inverse m | Nothing => Nothing
259 |       P r s   := apply m' p
260 |    in Just $ P (x11 + r * (x12 - x11)) (y11 + r * (y12 - y11))
261 |
262 | ||| Computes the distance of a point `p` from a line defined
263 | ||| by two of its points (`pl1` and `pl2`).
264 | export
265 | distanceToLine : {t : _} -> (p, pl1, pl2 : Point t) -> Maybe Double
266 | distanceToLine p pl1 pl2 =
267 |   let pp := perpendicularPoint p (translate (pl1 - pl2) p) 1 True
268 |    in distance p <$> intersect pl1 pl2 p pp
269 |
270 | ||| Given two reference points `pr1` and `pr2`, as well as
271 | ||| two points `p1` and `p2`, returns a linear transformation
272 | ||| which will superimpose `p1` with `pr1` and `p2` with `pr2`.
273 | |||
274 | ||| The linear transformation consists of the following steps:
275 | |||  1. translate `p1` to the origin
276 | |||  2. scale by the factor `|pr2-pr1|/|p2-p1|`.
277 | |||  3. rotate by `(angle (pr2-pr1) - angle(p2-p1))`
278 | |||  4. translate to `pr1`.
279 | export
280 | alignBond : {t : _} -> (pr1, pr2, p1, p2 : Point t) -> Point t -> Point t
281 | alignBond pr1 pr2 p1 p2 =
282 |  let Just ar := angle (pr2 - pr1) | Nothing => id
283 |      Just a  := angle (p2 - p1)   | Nothing => id
284 |      dr      := distance pr2 pr1
285 |      d       := distance p2 p1
286 |      True    := d > 0.0 | False => id
287 |      sc      := Scale.scale (dr/d)
288 |      da      := ar - a
289 |   in \p => translate (origin - p1) p |> scale sc |> rotate da |> translate (pr1-origin)
290 |
291 | ||| For a given point `p` and a list of points surrounding
292 | ||| `p`, computes the start and step angle to distribute
293 | ||| `n` additional points in the largest free section around
294 | ||| `p`.
295 | |||
296 | ||| This utility can be used to find the ideal placement of
297 | ||| a new bond to an atom with other surrounding atoms already
298 | ||| placed.
299 | export
300 | circularFreeSweep :
301 |      {t : _}
302 |   -> (n : Nat)
303 |   -> {auto prf : IsSucc n}
304 |   -> Point t
305 |   -> List (Point t)
306 |   -> (Angle,Angle)
307 | circularFreeSweep n p []        = (zero, fullSteps n)
308 | circularFreeSweep n p [x]       = (angleOrZero $ x - p, fullSteps $ S n)
309 | circularFreeSweep n p ps@(x::_) =
310 |  let ang  := angleOrZero $ p - center2d ps
311 |      angs := map (\y => angleOrZero $ y - p) ps
312 |   in case enclosingAngles ang angs of
313 |        Nothing      => (angleOrZero $ x - p, fullSteps $ S n)
314 |        Just (a1,a2) => (a1, divide (1+n) (a2-a1))
315 |
316 | ||| For two given points, generates a vector perpendicular to the
317 | ||| line connecting `x` and `y` and pointing towards a third given
318 | ||| point `c`.
319 | |||
320 | ||| This can be used to, for instance, place a double bond in the inner
321 | ||| side of a ring, or construct the center of a ring fused to another.
322 | export
323 | perpendicularTo : {t : _} -> (x,y,c : Point t) -> Vector (transform t)
324 | perpendicularTo x y c =
325 |  let cl := center2d (the (List _) [x,y])
326 |      vc := c - cl
327 |      v  := perpendicular (y-x)
328 |   in if dot v vc >= 0 then v else negate v
329 |
330 | ||| Like `perpendicularTo` but points away from the given center.
331 | export %inline
332 | perpendicularFrom : {t : _} -> (x,y,c : Point t) -> Vector (transform t)
333 | perpendicularFrom x y = negate . perpendicularTo x y
334 |