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