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