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