001  (ns walkmap.edge
002    "Essentially the specification for things we shall consider to be an edge.
003    An edge is a line segment having just a start and an end, with no intervening
004    nodes."
005    (:require [clojure.math.numeric-tower :as m]
006              [walkmap.utils :as u]
007              [walkmap.vertex :refer [canonicalise check-vertex ensure2d ensure3d vertex vertex= vertex?]]))
008  
009  (defn edge
010    "Return an edge between vertices `v1` and `v2`."
011    [v1 v2]
012    {:kind :edge
013     :walkmap.id/id (keyword (gensym "edge"))
014     :start (check-vertex v1)
015     :end (check-vertex v2)})
016  
017  (defn edge?
018    "True if `o` satisfies the conditions for a edge. An edge shall be a map
019    having the keys `:start` and `:end`, such that the values of each of those
020    keys shall be a vertex."
021    [o]
022    (and
023      (map? o)
024      (vertex? (:start o))
025      (vertex? (:end o))))
026  
027  (defn length
028    "Return the length of the edge `e`."
029    [e]
030    (let [start (ensure3d (:start e))
031          end (ensure3d (:end e))]
032      (m/sqrt
033        (reduce
034          +
035          (map
036            #(m/expt (- (% end) (% start)) 2)
037            [:x :y :z])))))
038  
039  (defn centre
040    "Return the vertex that represents the centre of this `edge`."
041    [edge]
042    (let [s (ensure3d (:start edge))
043          e (ensure3d (:end edge))]
044      (vertex
045        (+ (:x s) (/ (- (:x e) (:x s)) 2))
046        (+ (:y s) (/ (- (:y e) (:y s)) 2))
047        (+ (:z s) (/ (- (:z e) (:z s)) 2)))))
048  
049  (defn unit-vector
050    "Return an vertex parallel to `e` starting from the coordinate origin. Two
051    edges which are parallel will have the same unit vector."
052    [e]
053    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))}
054          l (length e')]
055      (canonicalise
056        (reduce
057          merge
058          {}
059          (map
060            (fn [k]
061              {k (/ (- (k (:end e')) (k (:start e'))) l)})
062            [:x :y :z])))))
063  
064  (defn parallel?
065    "True if all `edges` passed are parallel with one another."
066    [& edges]
067    (let [uvs (map unit-vector edges)]
068      (every?
069        #(vertex= % (first uvs))
070        (rest uvs))))
071  
072  (defn collinear?
073    "True if edges `e1` and `e2` are collinear with one another."
074    [e1 e2]
075    (parallel?
076      e1
077      e2
078      (if (vertex= (:start e1) (:start e2))
079        {:start (:start e1) :end (:end e2)}
080        {:start (:start e1) :end (:start e2)})))
081  
082  (defn collinear2d?
083    "True if the projections of edges `e1`, `e2` onto the x, y plane are
084    collinear."
085    [e1 e2]
086    (collinear? {:start (ensure2d (:start e1)) :end (ensure2d (:end e1))}
087                {:start (ensure2d (:start e2)) :end (ensure2d (:end e2))}))
088  
089  (defn minimaxd
090    "Apply function `f` to `coord` of the vertices at start and end of `edge`
091    and return the result. Intended use case is `f` = `min` or `max`, `coord`
092    is `:x`, `:y` or `:z`. No checks are made for sane arguments."
093    [edge coord f]
094    (apply f (list (coord (:start edge)) (coord (:end edge)))))
095  
096  (defn on?
097    "True if the vertex `v` is on the edge `e`."
098    [e v]
099    (let [p (ensure3d (:start e))
100          q (ensure3d v)
101          r (ensure3d (:end e))]
102      (and
103        (collinear? (edge p q) (edge q r))
104        (<= (:x q) (max (:x p) (:x r)))
105        (>= (:x q) (min (:x p) (:x r)))
106        (<= (:y q) (max (:y p) (:y r)))
107        (>= (:y q) (min (:y p) (:y r)))
108        (<= (:z q) (max (:z p) (:z r)))
109        (>= (:z q) (min (:z p) (:z r))))))
110  
111  (defn on2d?
112    "True if vertex `v` is on edge `e` when projected onto the x, y plane."
113    [e v]
114    (on? (edge (ensure2d (:start e)) (ensure2d (:end e))) v))
115  
116  (defn overlaps2d?
117    "True if the recangle in the x,y plane bisected by edge `e1` overlaps that
118    bisected by edge `e2`. It is an error if either `e1` or `e2` is not an edge."
119    [e1 e2]
120    (when (and (edge? e1) (edge? e2))
121      (and
122        (> (minimaxd e1 :x max) (minimaxd e2 :x min))
123        (< (minimaxd e1 :x min) (minimaxd e2 :x max))
124        (> (minimaxd e1 :y max) (minimaxd e2 :y min))
125        (< (minimaxd e1 :y min) (minimaxd e2 :y max)))))
126  
127  (defn intersection2d
128    "The probability of two lines intersecting in 3d space is low, and actually
129    that is mostly not something we're interested in. We're interested in
130    intersection in the `x,y` plane. This function returns a vertex representing
131    a point vertically over the intersection of edges `e1`, `e2` in the `x,y`
132    plane, whose `z` coordinate is
133  
134    * 0 if both edges are 2d (i.e. have missing or zero `z` coordinates);
135    * if one edge is 2d, then the point on the other edge over the intersection;
136    * otherwise, the average of the z coordinates of the points on the two
137    edges over the intersection.
138  
139    If no such intersection exists, `nil` is returned.
140  
141    It is an error, and an exception will be thrown, if either `e1` or `e2` is
142    not an edge."
143    [e1 e2]
144    (if (and (edge? e1) (edge? e2))
145      (when
146        (overlaps2d? e1 e2) ;; relatively cheap check
147        (if
148          (collinear2d? e1 e2)
149          ;; any point within the overlap will do, but we'll pick the end of e1
150          ;; which is on e2
151          (if (on2d? e2 (:start e1)) (:start e1) (:end e1))
152          ;; blatantly stolen from
153          ;; https://gist.github.com/cassiel/3e725b49670356a9b936
154          (let [x1 (:x (:start e1))
155                x2 (:x (:end e1))
156                x3 (:x (:start e2))
157                x4 (:x (:end e2))
158                y1 (:y (:start e1))
159                y2 (:y (:end e1))
160                y3 (:y (:start e2))
161                y4 (:y (:end e2))
162                denom (- (* (- x1 x2) (- y3 y4))
163                         (* (- y1 y2) (- x3 x4)))
164                x1y2-y1x2 (- (* x1 y2) (* y1 x2))
165                x3y4-y3x4 (- (* x3 y4) (* y3 x4))
166                px-num (- (* x1y2-y1x2 (- x3 x4))
167                          (* (- x1 x2) x3y4-y3x4))
168                py-num (- (* x1y2-y1x2 (- y3 y4))
169                          (* (- y1 y2) x3y4-y3x4))
170                result (when-not (zero? denom)
171                         (vertex (/ px-num denom) (/ py-num denom)))]
172            (when (and result (on2d? e1 result) (on2d? e2 result)) result))))
173      (throw (IllegalArgumentException.
174               (str
175                 "Both `e1` and `e2` must be edges."
176                 (map #(or (:kind %) (type %)) [e1 e2]))))))
177