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.")))