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 {:vertices (check-vertices vertices)
056 :walkmap.id/id (keyword (gensym "poly"))
057 :kind :polygon})
058
059 (defn rectangle
060 "Return a rectangle, with edges aligned east-west and north-south, whose
061 south-west corner is the vertex `vsw` and whose north-east corner is the
062 vertex `vne`."
063 [vsw vne]
064 ;; we can actually create any rectangle in the xy plane based on two opposite
065 ;; corners, but the maths are a bit to advanced for me today. TODO: do it!
066 (let [vnw (vertex (:x (check-vertex vsw))
067 (:y (check-vertex vne))
068 (/ (reduce + (map #(or (:z %) 0) [vsw vne])) 2))
069 vse (vertex (:x vne)
070 (:y vsw)
071 (/ (reduce + (map #(or (:z %) 0) [vsw vne])) 2))]
072 (t/tag (polygon vsw vnw vne vse) :rectangle)))
073
074 ;; (rectangle (vertex 1 2 3) (vertex 7 9 4))
075
076 (defn gradient
077 "Return a polygon like `triangle` but with a key `:gradient` whose value is a
078 unit vector representing the gradient across `triangle`."
079 [triangle]
080 (let [order (sort #(max (:z %1) (:z %2))
081 (:vertices (check-triangle triangle)))
082 highest (first order)
083 lowest (last order)]
084 (assoc triangle :gradient (e/unit-vector (e/edge lowest highest)))))
085
086 (defn triangle-centre
087 "Return a canonicalised `facet` (i.e. a triangular polygon) with an added
088 key `:centre` whose value represents the centre of this facet in 3
089 dimensions. This only works for triangles, so is here not in
090 `walkmap.polygon`. It is an error (although no exception is currently
091 thrown) if the object past is not a triangular polygon."
092 [facet]
093 (let [vs (:vertices (check-triangle facet))
094 v1 (first vs)
095 opposite (e/edge (nth vs 1) (nth vs 2))
096 oc (e/centre opposite)]
097 (assoc
098 facet
099 :centre
100 (vertex
101 (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3))
102 (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3))
103 (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3))))))
104
105 (defn centre
106 [poly]
107 (case (count (:vertices (check-polygon poly)))
108 3 (triangle-centre poly)
109 ;; else
110 (throw
111 (UnsupportedOperationException.
112 "The general case of centre for polygons is not yet implemented."))))
113
114