001  (ns walkmap.polygon
002    "Essentially the specification for things we shall consider to be polygons."
003    (:require [clojure.string :as s]
004              [walkmap.edge :as e]
005              [walkmap.tag :as t]
006              [walkmap.utils :refer [check-kind-type
007                                     check-kind-type-seq
008                                     kind-type
009                                     not-yet-implemented]]
010              [walkmap.vertex :refer [check-vertex check-vertices vertex vertex?]]))
011  
012  (defn polygon?
013    "True if `o` satisfies the conditions for a polygon. A polygon shall be a
014    map which has a value for the key `:vertices`, where that value is a sequence
015    of vertices."
016    [o]
017    (let
018      [v (:vertices o)]
019      (and
020        (coll? v)
021        (> (count v) 2)
022        (every? vertex? v)
023        (:walkmap.id/id o)
024        (or (nil? (:kind o)) (= (:kind o) :polygon)))))
025  
026  (defmacro check-polygon
027    "If `o` is not a polygon, throw an `IllegalArgumentException` with an
028    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
029    from the calling function."
030    [o]
031    `(check-kind-type ~o polygon? :polygon))
032  
033  (defmacro check-polygons
034    "If `o` is not a sequence of polygons, throw an `IllegalArgumentException` with an
035    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
036    from the calling function."
037    [o]
038    `(check-kind-type-seq ~o polygon? :polygon))
039  
040  (defn triangle?
041    "True if `o` satisfies the conditions for a triangle. A triangle shall be a
042    polygon with exactly three vertices."
043    [o]
044    (and
045      (coll? o)
046      (= (count (:vertices o)) 3)))
047  
048  (defmacro check-triangle
049    "If `o` is not a triangle, throw an `IllegalArgumentException` with an
050    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
051    from the calling function."
052    [o]
053    `(check-kind-type ~o triangle? :triangle))
054  
055  (defn polygon
056    "Return a polygon constructed from these `vertices`."
057    [& vertices]
058    (if
059      (> (count vertices) 2)
060      {:vertices (check-vertices vertices)
061       :walkmap.id/id (keyword (gensym "poly"))
062       :kind :polygon}
063      (throw (IllegalArgumentException.
064               "A polygon must have at least 3 vertices."))))
065  
066  (defn rectangle
067    "Return a rectangle, with edges aligned east-west and north-south, whose
068    south-west corner is the vertex `vsw` and whose north-east corner is the
069    vertex `vne`."
070    [vsw vne]
071    ;; we can actually create any rectangle in the xy plane based on two opposite
072    ;; corners, but the maths are a bit to advanced for me today. TODO: do it!
073    (let [vnw (vertex (:x (check-vertex vsw))
074                      (:y (check-vertex vne))
075                      (/ (reduce + (map #(or (:z %) 0) [vsw vne])) 2))
076          vse (vertex (:x vne)
077                      (:y vsw)
078                      (/ (reduce + (map #(or (:z %) 0) [vsw vne])) 2))
079          height-order (sort-by :z [vsw vne])]
080      (t/tag
081        (assoc
082          (polygon vsw vnw vne vse)
083          :gradient
084          (e/unit-vector (e/edge (first height-order) (last height-order)))
085          :centre
086          (vertex (+ (:x vsw) (/ (- (:x vne) (:x vsw)) 2))
087                  (+ (:x vsw) (/ (- (:y vne) (:y vsw)) 2))
088                  (:z vse)))
089        :rectangle)))
090  
091  ;; (rectangle (vertex 1 2 3) (vertex 7 9 4))
092  
093  (defn gradient
094    "Return a polygon like `triangle` but with a key `:gradient` whose value is a
095    unit vector representing the gradient across `triangle`."
096    [triangle]
097    (let [order (sort #(max (:z %1) (:z %2))
098                      (:vertices (check-triangle triangle)))
099          highest (first order)
100          lowest (last order)]
101       (assoc triangle :gradient (e/unit-vector (e/edge lowest highest)))))
102  
103  (defn triangle-centre
104    "Return a canonicalised `facet` (i.e. a triangular polygon) with an added
105    key `:centre` whose value represents the centre of this facet in 3
106    dimensions. This only works for triangles, so is here not in
107    `walkmap.polygon`. It is an error (although no exception is currently
108    thrown) if the object past is not a triangular polygon."
109    [facet]
110    (let [vs (:vertices (check-triangle facet))
111          v1 (first vs)
112          opposite (e/edge (nth vs 1) (nth vs 2))
113          oc (e/centre opposite)]
114        (assoc
115        facet
116        :centre
117        (vertex
118          (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3))
119          (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3))
120          (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3))))))
121  
122  (defn centre
123    [poly]
124    (case (count (:vertices (check-polygon poly)))
125      3 (triangle-centre poly)
126      ;; else
127      (throw
128        (UnsupportedOperationException.
129          "The general case of centre for polygons is not yet implemented."))))
130  
131  (defmacro on2dtriangle?
132    "Is the projection of this `vertex` on the x, y plane within the
133    projection of this triangle on that plane?"
134    [vertex poly]
135    `(not-yet-implemented "on2d? for triangles."))
136  
137  (defn on2drectangle?
138    "Is the projection of this `vertex` on the x, y plane within the
139    projection of this rectangle on that plane?"
140    [vertex rectangle]
141    (let [xo (sort-by :x (:vertices rectangle))
142          yo (sort-by :x (:vertices rectangle))]
143      (and
144        (< (:x (first xo)) (:x vertex) (:x (last xo)))
145        (< (:y (first yo)) (:y vertex) (:y (last yo))))))
146  
147  (defmacro on2d?
148    "Is the projection of this `vertex` on the x, y plane within the
149    projection of this polygon `poly` on that plane?"
150    [vertex poly]
151    `(cond
152      (rectangle? ~poly) (on2drectangle? ~vertex ~poly)
153      (triangle? ~poly) (on2dtriangle? ~vertex ~poly)
154      :else
155      (not-yet-implemented "general case of on2d? for polygons.")))