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 (polygon vsw vnw vne vse) :rectangle)))
077  
078  ;; (rectangle (vertex 1 2 3) (vertex 7 9 4))
079  
080  (defn gradient
081    "Return a polygon like `triangle` but with a key `:gradient` whose value is a
082    unit vector representing the gradient across `triangle`."
083    [triangle]
084    (let [order (sort #(max (:z %1) (:z %2))
085                      (:vertices (check-triangle triangle)))
086          highest (first order)
087          lowest (last order)]
088       (assoc triangle :gradient (e/unit-vector (e/edge lowest highest)))))
089  
090  (defn triangle-centre
091    "Return a canonicalised `facet` (i.e. a triangular polygon) with an added
092    key `:centre` whose value represents the centre of this facet in 3
093    dimensions. This only works for triangles, so is here not in
094    `walkmap.polygon`. It is an error (although no exception is currently
095    thrown) if the object past is not a triangular polygon."
096    [facet]
097    (let [vs (:vertices (check-triangle facet))
098          v1 (first vs)
099          opposite (e/edge (nth vs 1) (nth vs 2))
100          oc (e/centre opposite)]
101        (assoc
102        facet
103        :centre
104        (vertex
105          (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3))
106          (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3))
107          (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3))))))
108  
109  (defn centre
110    [poly]
111    (case (count (:vertices (check-polygon poly)))
112      3 (triangle-centre poly)
113      ;; else
114      (throw
115        (UnsupportedOperationException.
116          "The general case of centre for polygons is not yet implemented."))))
117  
118