diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 046068e..ff0a0f9 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -16,18 +16,18 @@ walkmap.edge
701
703
12
98.32 %
99
100
5
100.00 % -17517104 +17717105 walkmap.id
walkmap.ocean
18
10
-64.29 % + style="width:100.0%; + float:left;"> 28
+100.00 %
7
1
-87.50 % -2448 + style="width:100.0%; + float:left;"> 8 +100.00 % +2548 walkmap.path
23
168
-12.04 % + style="width:93.60902255639098%; + float:left;"> 249
17
+93.61 %
7
35
3
1
31
-20.51 % -84739 + float:left;"> 1 +97.44 % +94939 walkmap.polygon
197
75
-72.43 % + style="width:91.85393258426966%; + float:left;"> 327
29
+91.85 %
32
5
13
-74.00 % -87850 + style="width:89.58333333333333%; + float:left;"> 43
5
+100.00 % +971148 walkmap.read-svg
walkmap.stl
238
247
-49.07 % + style="width:49.361702127659576%; + float:left;"> 232
+50.64 %
43
walkmap.superstructure
272
257
107
-71.77 % +70.60 %
62
walkmap.svg
11
282
-3.75 % + style="width:95.78544061302682%; + float:left;"> 250
+4.21 %
8
walkmap.tag
181
+ float:left;"> 162
100.00 %
walkmap.utils
92
28
-76.67 % + style="width:52.91005291005291%; + float:left;"> 400
356
+52.91 %
16
1
3
-85.00 % -43420 + style="width:63.888888888888886%; + float:left;"> 23
3
10
+72.22 % +101936 walkmap.vertex
304
153
-66.52 % + style="width:69.0909090909091%; + float:left;"> 380
170
+69.09 %
49
9
24
-70.73 % -1481382 + style="width:63.013698630136986%; + float:left;"> 46
15
12
+83.56 % +1491573 Totals: -60.81 % +66.22 % -62.17 % +70.05 % diff --git a/docs/cloverage/walkmap/edge.clj.html b/docs/cloverage/walkmap/edge.clj.html index 6d8db3e..5d60f3e 100644 --- a/docs/cloverage/walkmap/edge.clj.html +++ b/docs/cloverage/walkmap/edge.clj.html @@ -20,514 +20,520 @@ 005    (:require [clojure.math.numeric-tower :as m]
- 006              [walkmap.vertex :refer [ensure2d ensure3d vertex vertex= vertex?]])) + 006              [walkmap.utils :as u] +
+ + 007              [walkmap.vertex :refer [canonicalise ensure2d ensure3d vertex vertex= vertex?]]))
- 007   + 008  
- 008  (defn edge + 009  (defn edge
- 009    "Return an edge between vertices `v1` and `v2`." + 010    "Return an edge between vertices `v1` and `v2`."
- 010    [v1 v2] + 011    [v1 v2]
- 011    (if + 012    (if
- 012      (and (vertex? v1) (vertex? v2)) + 013      (and (vertex? v1) (vertex? v2))
- 013      {:kind :edge :walkmap.id/id (keyword (gensym "edge")) :start v1 :end v2} + 014      {:kind :edge :walkmap.id/id (keyword (gensym "edge")) :start v1 :end v2}
- 014      (throw (IllegalArgumentException. "Must be vertices.")))) + 015      (throw (IllegalArgumentException. "Must be vertices."))))
- 015   + 016  
- 016  (defn edge? + 017  (defn edge?
- 017    "True if `o` satisfies the conditions for a edge. An edge shall be a map + 018    "True if `o` satisfies the conditions for a edge. An edge shall be a map
- 018    having the keys `:start` and `:end`, such that the values of each of those + 019    having the keys `:start` and `:end`, such that the values of each of those
- 019    keys shall be a vertex." + 020    keys shall be a vertex."
- 020    [o] + 021    [o]
- 021    (and + 022    (and
- 022      (map? o) + 023      (map? o)
- 023      (vertex? (:start o)) + 024      (vertex? (:start o))
- 024      (vertex? (:end o)))) + 025      (vertex? (:end o))))
- 025   + 026  
- 026  (defn length + 027  (defn length
- 027    "Return the length of the edge `e`." + 028    "Return the length of the edge `e`."
- 028    [e] + 029    [e]
- 029    (let [start (ensure3d (:start e)) + 030    (let [start (ensure3d (:start e))
- 030          end (ensure3d (:end e))] + 031          end (ensure3d (:end e))]
- 031      (m/sqrt + 032      (m/sqrt
- 032        (reduce + 033        (reduce
- 033          + + 034          +
- 034          (map + 035          (map
- 035            #(m/expt (- (% end) (% start)) 2) + 036            #(m/expt (- (% end) (% start)) 2)
- 036            [:x :y :z]))))) + 037            [:x :y :z])))))
- 037   + 038  
- 038  (defn centre + 039  (defn centre
- 039    "Return the vertex that represents the centre of this `edge`." + 040    "Return the vertex that represents the centre of this `edge`."
- 040    [edge] + 041    [edge]
- 041    (let [s (ensure3d (:start edge)) + 042    (let [s (ensure3d (:start edge))
- 042          e (ensure3d (:end edge))] + 043          e (ensure3d (:end edge))]
- 043      (vertex + 044      (vertex
- 044        (+ (:x s) (/ (- (:x e) (:x s)) 2)) + 045        (+ (:x s) (/ (- (:x e) (:x s)) 2))
- 045        (+ (:y s) (/ (- (:y e) (:y s)) 2)) + 046        (+ (:y s) (/ (- (:y e) (:y s)) 2))
- 046        (+ (:z s) (/ (- (:z e) (:z s)) 2))))) + 047        (+ (:z s) (/ (- (:z e) (:z s)) 2)))))
- 047   + 048  
- 048  (defn unit-vector + 049  (defn unit-vector
- 049    "Return an vertex parallel to `e` starting from the coordinate origin. Two + 050    "Return an vertex parallel to `e` starting from the coordinate origin. Two
- 050    edges which are parallel will have the same unit vector." + 051    edges which are parallel will have the same unit vector."
- 051    [e] + 052    [e]
- 052    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} + 053    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))}
- 053          l (length e')] + 054          l (length e')]
- 054      (reduce -
- - 055        merge -
- - 056        {} + 055      (canonicalise
- 057        (map + 056        (reduce
- 058          (fn [k] + 057          merge +
+ + 058          {} +
+ + 059          (map +
+ + 060            (fn [k]
- 059            {k (/ (- (k (:end e')) (k (:start e'))) l)}) + 061              {k (/ (- (k (:end e')) (k (:start e'))) l)})
- 060          [:x :y :z])))) + 062            [:x :y :z])))))
- 061   + 063  
- 062  (defn parallel? + 064  (defn parallel?
- 063    "True if all `edges` passed are parallel with one another." + 065    "True if all `edges` passed are parallel with one another."
- 064    [& edges] + 066    [& edges]
- 065    (let [uvs (map unit-vector edges)] + 067    (let [uvs (map unit-vector edges)]
- 066      (every? + 068      (every?
- 067        #(vertex= % (first uvs)) + 069        #(vertex= % (first uvs))
- 068        (rest uvs)))) + 070        (rest uvs))))
- 069   + 071  
- 070  (defn collinear? + 072  (defn collinear?
- 071    "True if edges `e1` and `e2` are collinear with one another." + 073    "True if edges `e1` and `e2` are collinear with one another."
- 072    [e1 e2] + 074    [e1 e2]
- 073    (parallel? + 075    (parallel?
- 074      e1 + 076      e1
- 075      e2 + 077      e2
- 076      (if (vertex= (:start e1) (:start e2)) + 078      (if (vertex= (:start e1) (:start e2))
- 077        {:start (:start e1) :end (:end e2)} + 079        {:start (:start e1) :end (:end e2)}
- 078        {:start (:start e1) :end (:start e2)}))) + 080        {:start (:start e1) :end (:start e2)})))
- 079   + 081  
- 080  (defn collinear2d? + 082  (defn collinear2d?
- 081    "True if the projections of edges `e1`, `e2` onto the x, y plane are + 083    "True if the projections of edges `e1`, `e2` onto the x, y plane are
- 082    collinear." + 084    collinear."
- 083    [e1 e2] + 085    [e1 e2]
- 084    (collinear? {:start (ensure2d (:start e1)) :end (ensure2d (:end e1))} + 086    (collinear? {:start (ensure2d (:start e1)) :end (ensure2d (:end e1))}
- 085                {:start (ensure2d (:start e2)) :end (ensure2d (:end e2))})) + 087                {:start (ensure2d (:start e2)) :end (ensure2d (:end e2))}))
- 086   + 088  
- 087  (defn minimaxd + 089  (defn minimaxd
- 088    "Apply function `f` to `coord` of the vertices at start and end of `edge` + 090    "Apply function `f` to `coord` of the vertices at start and end of `edge`
- 089    and return the result. Intended use case is `f` = `min` or `max`, `coord` + 091    and return the result. Intended use case is `f` = `min` or `max`, `coord`
- 090    is `:x`, `:y` or `:z`. No checks are made for sane arguments." + 092    is `:x`, `:y` or `:z`. No checks are made for sane arguments."
- 091    [edge coord f] + 093    [edge coord f]
- 092    (apply f (list (coord (:start edge)) (coord (:end edge))))) + 094    (apply f (list (coord (:start edge)) (coord (:end edge)))))
- 093   + 095  
- 094  (defn on? + 096  (defn on?
- 095    "True if the vertex `v` is on the edge `e`." + 097    "True if the vertex `v` is on the edge `e`."
- 096    [e v] + 098    [e v]
- 097    (let [p (ensure3d (:start e)) + 099    (let [p (ensure3d (:start e))
- 098          q (ensure3d v) + 100          q (ensure3d v)
- 099          r (ensure3d (:end e))] + 101          r (ensure3d (:end e))]
- 100      (and + 102      (and
- 101        (collinear? (edge p q) (edge q r)) + 103        (collinear? (edge p q) (edge q r))
- 102        (<= (:x q) (max (:x p) (:x r))) + 104        (<= (:x q) (max (:x p) (:x r)))
- 103        (>= (:x q) (min (:x p) (:x r))) + 105        (>= (:x q) (min (:x p) (:x r)))
- 104        (<= (:y q) (max (:y p) (:y r))) + 106        (<= (:y q) (max (:y p) (:y r)))
- 105        (>= (:y q) (min (:y p) (:y r))) + 107        (>= (:y q) (min (:y p) (:y r)))
- 106        (<= (:z q) (max (:z p) (:z r))) + 108        (<= (:z q) (max (:z p) (:z r)))
- 107        (>= (:z q) (min (:z p) (:z r)))))) + 109        (>= (:z q) (min (:z p) (:z r))))))
- 108   + 110  
- 109  (defn on2d? + 111  (defn on2d?
- 110    "True if vertex `v` is on edge `e` when projected onto the x, y plane." + 112    "True if vertex `v` is on edge `e` when projected onto the x, y plane."
- 111    [e v] + 113    [e v]
- 112    (on? (edge (ensure2d (:start e)) (ensure2d (:end e))) v)) + 114    (on? (edge (ensure2d (:start e)) (ensure2d (:end e))) v))
- 113   + 115  
- 114  (defn overlaps2d? + 116  (defn overlaps2d?
- 115    "True if the recangle in the x,y plane bisected by edge `e1` overlaps that + 117    "True if the recangle in the x,y plane bisected by edge `e1` overlaps that
- 116    bisected by edge `e2`. It is an error if either `e1` or `e2` is not an edge." + 118    bisected by edge `e2`. It is an error if either `e1` or `e2` is not an edge."
- 117    [e1 e2] + 119    [e1 e2]
- 118    (when (and (edge? e1) (edge? e2)) + 120    (when (and (edge? e1) (edge? e2))
- 119      (and + 121      (and
- 120        (> (minimaxd e1 :x max) (minimaxd e2 :x min)) + 122        (> (minimaxd e1 :x max) (minimaxd e2 :x min))
- 121        (< (minimaxd e1 :x min) (minimaxd e2 :x max)) + 123        (< (minimaxd e1 :x min) (minimaxd e2 :x max))
- 122        (> (minimaxd e1 :y max) (minimaxd e2 :y min)) + 124        (> (minimaxd e1 :y max) (minimaxd e2 :y min))
- 123        (< (minimaxd e1 :y min) (minimaxd e2 :y max))))) + 125        (< (minimaxd e1 :y min) (minimaxd e2 :y max)))))
- 124   + 126  
- 125  (defn intersection2d + 127  (defn intersection2d
- 126    "The probability of two lines intersecting in 3d space is low, and actually + 128    "The probability of two lines intersecting in 3d space is low, and actually
- 127    that is mostly not something we're interested in. We're interested in + 129    that is mostly not something we're interested in. We're interested in
- 128    intersection in the `x,y` plane. This function returns a vertex representing + 130    intersection in the `x,y` plane. This function returns a vertex representing
- 129    a point vertically over the intersection of edges `e1`, `e2` in the `x,y` + 131    a point vertically over the intersection of edges `e1`, `e2` in the `x,y`
- 130    plane, whose `z` coordinate is + 132    plane, whose `z` coordinate is
- 131   + 133  
- 132    * 0 if both edges are 2d (i.e. have missing or zero `z` coordinates); + 134    * 0 if both edges are 2d (i.e. have missing or zero `z` coordinates);
- 133    * if one edge is 2d, then the point on the other edge over the intersection; + 135    * if one edge is 2d, then the point on the other edge over the intersection;
- 134    * otherwise, the average of the z coordinates of the points on the two + 136    * otherwise, the average of the z coordinates of the points on the two
- 135    edges over the intersection. -
- - 136   -
- - 137    If no such intersection exists, `nil` is returned. + 137    edges over the intersection.
138  
- 139    It is an error, and an exception will be thrown, if either `e1` or `e2` is -
- - 140    not an edge." -
- - 141    [e1 e2] -
- - 142    (if (and (edge? e1) (edge? e2)) -
- - 143      (when -
- - 144        (overlaps2d? e1 e2) ;; relatively cheap check -
- - 145        (if -
- - 146          (collinear2d? e1 e2) -
- - 147          ;; any point within the overlap will do, but we'll pick the end of e1 -
- - 148          ;; which is on e2 -
- - 149          (if (on2d? e2 (:start e1)) (:start e1) (:end e1)) -
- - 150          ;; blatantly stolen from -
- - 151          ;; https://gist.github.com/cassiel/3e725b49670356a9b936 -
- - 152          (let [x1 (:x (:start e1)) -
- - 153                x2 (:x (:end e1)) -
- - 154                x3 (:x (:start e2)) -
- - 155                x4 (:x (:end e2)) -
- - 156                y1 (:y (:start e1)) -
- - 157                y2 (:y (:end e1)) -
- - 158                y3 (:y (:start e2)) -
- - 159                y4 (:y (:end e2)) -
- - 160                denom (- (* (- x1 x2) (- y3 y4)) -
- - 161                         (* (- y1 y2) (- x3 x4))) -
- - 162                x1y2-y1x2 (- (* x1 y2) (* y1 x2)) -
- - 163                x3y4-y3x4 (- (* x3 y4) (* y3 x4)) -
- - 164                px-num (- (* x1y2-y1x2 (- x3 x4)) -
- - 165                          (* (- x1 x2) x3y4-y3x4)) -
- - 166                py-num (- (* x1y2-y1x2 (- y3 y4)) -
- - 167                          (* (- y1 y2) x3y4-y3x4)) -
- - 168                result (when-not (zero? denom) -
- - 169                         (vertex (/ px-num denom) (/ py-num denom)))] -
- - 170            (when (and result (on2d? e1 result) (on2d? e2 result)) result)))) -
- - 171      (throw (IllegalArgumentException. -
- - 172               (str -
- - 173                 "Both `e1` and `e2` must be edges." -
- - 174                 (map #(or (:kind %) (type %)) [e1 e2])))))) + 139    If no such intersection exists, `nil` is returned.
- 175   + 140   +
+ + 141    It is an error, and an exception will be thrown, if either `e1` or `e2` is +
+ + 142    not an edge." +
+ + 143    [e1 e2] +
+ + 144    (if (and (edge? e1) (edge? e2)) +
+ + 145      (when +
+ + 146        (overlaps2d? e1 e2) ;; relatively cheap check +
+ + 147        (if +
+ + 148          (collinear2d? e1 e2) +
+ + 149          ;; any point within the overlap will do, but we'll pick the end of e1 +
+ + 150          ;; which is on e2 +
+ + 151          (if (on2d? e2 (:start e1)) (:start e1) (:end e1)) +
+ + 152          ;; blatantly stolen from +
+ + 153          ;; https://gist.github.com/cassiel/3e725b49670356a9b936 +
+ + 154          (let [x1 (:x (:start e1)) +
+ + 155                x2 (:x (:end e1)) +
+ + 156                x3 (:x (:start e2)) +
+ + 157                x4 (:x (:end e2)) +
+ + 158                y1 (:y (:start e1)) +
+ + 159                y2 (:y (:end e1)) +
+ + 160                y3 (:y (:start e2)) +
+ + 161                y4 (:y (:end e2)) +
+ + 162                denom (- (* (- x1 x2) (- y3 y4)) +
+ + 163                         (* (- y1 y2) (- x3 x4))) +
+ + 164                x1y2-y1x2 (- (* x1 y2) (* y1 x2)) +
+ + 165                x3y4-y3x4 (- (* x3 y4) (* y3 x4)) +
+ + 166                px-num (- (* x1y2-y1x2 (- x3 x4)) +
+ + 167                          (* (- x1 x2) x3y4-y3x4)) +
+ + 168                py-num (- (* x1y2-y1x2 (- y3 y4)) +
+ + 169                          (* (- y1 y2) x3y4-y3x4)) +
+ + 170                result (when-not (zero? denom) +
+ + 171                         (vertex (/ px-num denom) (/ py-num denom)))] +
+ + 172            (when (and result (on2d? e1 result) (on2d? e2 result)) result)))) +
+ + 173      (throw (IllegalArgumentException. +
+ + 174               (str +
+ + 175                 "Both `e1` and `e2` must be edges." +
+ + 176                 (map #(or (:kind %) (type %)) [e1 e2])))))) +
+ + 177  
diff --git a/docs/cloverage/walkmap/ocean.clj.html b/docs/cloverage/walkmap/ocean.clj.html index f729ad3..81adc06 100644 --- a/docs/cloverage/walkmap/ocean.clj.html +++ b/docs/cloverage/walkmap/ocean.clj.html @@ -8,73 +8,76 @@ 001  (ns walkmap.ocean
- 002    "Deal with (specifically, at this stage, cull) ocean areas") + 002    "Deal with (specifically, at this stage, cull) ocean areas" +
+ + 003    (:require [walkmap.utils :refer [=ish]]))
- 003   + 004  
- 004  (def ^:dynamic *sea-level* + 005  (def ^:dynamic *sea-level*
- 005    "The sea level on heightmaps we're currently handling. If characters are to + 006    "The sea level on heightmaps we're currently handling. If characters are to
- 006    be able to swin in the sea, we must model the sea bottom, so we need + 007    be able to swin in the sea, we must model the sea bottom, so we need
- 007    heightmaps which cover at least the continental shelf. However, the sea + 008    heightmaps which cover at least the continental shelf. However, the sea
- 008    bottom is not walkable territory and can be culled from walkmaps. + 009    bottom is not walkable territory and can be culled from walkmaps.
- 009   + 010  
- 010    **Note** must be a floating point number. `(= 0 0.0)` returns `false`!" + 011    **Note** must be a floating point number. `(= 0 0.0)` returns `false`!"
- 011    0.0) + 012    0.0)
- 012   + 013  
- 013  (defn ocean? + 014  (defn ocean?
- 014    "Of a `facet`, is the altitude of every vertice equal to `*sea-level*`?" + 015    "Of a `facet`, is the altitude of every vertice equal to `*sea-level*`?"
- 015    [facet] + 016    [facet]
- 016    (every? + 017    (every?
- 017      #(= % *sea-level*) + 018      #(=ish % *sea-level*)
- 018      (map :z (:vertices facet)))) + 019      (map :z (:vertices facet))))
- 019   + 020  
- 020  (defn cull-ocean-facets + 021  (defn cull-ocean-facets
- 021    "Ye cannae walk on water. Remove all facets from this `stl` structure which + 022    "Ye cannae walk on water. Remove all facets from this `stl` structure which
- 022    are at sea level." + 023    are at sea level."
- 023    [stl] + 024    [stl]
- - 024    (assoc stl :facets (remove ocean? (:facets stl)))) + + 025    (assoc stl :facets (remove ocean? (:facets stl))))
diff --git a/docs/cloverage/walkmap/path.clj.html b/docs/cloverage/walkmap/path.clj.html index ea999df..907a2e7 100644 --- a/docs/cloverage/walkmap/path.clj.html +++ b/docs/cloverage/walkmap/path.clj.html @@ -23,238 +23,268 @@ 006              [walkmap.edge :as e]

- 007              [walkmap.polygon :refer [polygon?]] + 007              [walkmap.polygon :refer [check-polygon polygon?]]
- 008              [walkmap.utils :refer [kind-type]] + 008              [walkmap.tag :refer [tag tags]]
- 009              [walkmap.vertex :refer [vertex?]])) + 009              [walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]] +
+ + 010              [walkmap.vertex :refer [vertex?]]))
- 010   + 011  
- 011  (defn path? + 012  (defn path?
- 012    "True if `o` satisfies the conditions for a path. A path shall be a map + 013    "True if `o` satisfies the conditions for a path. A path shall be a map
- 013    having the key `:vertices`, whose value shall be a sequence of vertices as + 014    having the key `:vertices`, whose value shall be a sequence of vertices as
- 014    defined in `walkmap.vertex`." + 015    defined in `walkmap.vertex`."
- 015    [o] + 016    [o]
- - 016    (let + + 017    (let
- - 017      [v (:vertices o)] + + 018      [v (:vertices o)]
- - 018      (and + + 019      (and
- - 019        (seq? v) + + 020        (seq? v)
- - 020        (> (count v) 2) + + 021        (> (count v) 1)
- - 021        (every? vertex? v) + + 022        (every? vertex? v)
- - 022        (:walkmap.id/id o) + + 023        (:walkmap.id/id o)
- - 023        (or (nil? (:kind o)) (= (:kind o) :path))))) + + 024        (or (nil? (:kind o)) (= (:kind o) :path)))))
- 024   + 025  
- 025  (defn path + 026  (defn path
- 026    "Return a path constructed from these `vertices`." + 027    "Return a path constructed from these `vertices`."
- 027    [& vertices] + 028    [& vertices]
- - 028    (when-not (every? vertex? vertices) + + 029    (check-kind-type-seq vertices vertex? :vertex)
- - 029      (throw (IllegalArgumentException. + + 030    (if
- - 030               (str -
- - 031                 "Each item on path must be a vertex: " -
- - 032                 (s/join " " (map kind-type (remove vertex? vertices))))))) + + 031      (> (count vertices) 1)
- 033    {:vertices vertices :walkmap.id/id (keyword (gensym "path")) :kind :path}) + 032      {:vertices vertices :walkmap.id/id (keyword (gensym "path")) :kind :path} +
+ + 033      (throw (IllegalArgumentException. "Path must have more than one vertex."))))
034  
- - 035  (defn polygon->path + + 035  (defmacro check-path
- 036    "If `o` is a polygon, return an equivalent path. What's different about + 036    "If `o` is not a path, throw an `IllegalArgumentException` with an
- 037    a path is that in polygons there is an implicit edge between the first + 037    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
- 038    vertex and the last. In paths, there isn't, so we need to add that + 038    from the calling function."
- 039    edge explicitly. -
- - 040   -
- - 041    If `o` is not a polygon, will throw an exception." -
- - 042    [o] -
- - 043    (when-not (polygon? o) -
- - 044      (throw (IllegalArgumentException. (str "Not a polygon: " (kind-type o))))) -
- - 045    (assoc (dissoc o :vertices) -
- - 046      :kind :path -
- - 047      ;; `concat` rather than `conj` because order matters. -
- - 048      :vertices (concat (:vertices o) (list (first (:vertices o)))))) -
- - 049   + 039    [o]
- 050  (defn path->edges -
- - 051    "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of -
- - 052    edges representing that path, polygon or sequence. + 040    `(check-kind-type ~o path? :path))
- 053   + 041   +
+ + 042  (defmacro check-paths
- 054    Throws `IllegalArgumentException` if `o` is not a path, a polygon, or + 043    "If `o` is not a sequence of paths, throw an `IllegalArgumentException` with an
- 055    sequence of vertices." + 044    appropriate message; otherwise, returns `o`. Macro, so exception is thrown +
+ + 045    from the calling function." +
+ + 046    [o] +
+ + 047    `(check-kind-type-seq ~o path? :path)) +
+ + 048   +
+ + 049  (defn polygon->path +
+ + 050    "If `o` is a polygon, return an equivalent path. What's different about +
+ + 051    a path is that in polygons there is an implicit edge between the first +
+ + 052    vertex and the last. In paths, there isn't, so we need to add that +
+ + 053    edge explicitly. +
+ + 054   +
+ + 055    If `o` is not a polygon, will throw an exception."
056    [o]
- - 057    (cond -
- - 058      (seq? o) -
- - 059      (when -
- - 060        (and -
- - 061          (vertex? (first o)) -
- - 062          (vertex? (first (rest o)))) -
- - 063        (cons + + 057  ;; this is breaking, but I have NO IDEA why!
- 064          ;; TODO: think about: when constructing an edge from a path, should the + 058  ;;  (check-polygon o polygon? :polygon) +
+ + 059    (assoc (dissoc o :vertices)
- 065          ;; constructed edge be tagged with the tags of the path? -
- - 066          (e/edge (first o) (rest o)) -
- - 067          (path->edges (rest o)))) -
- - 068      (path? o) -
- - 069      (path->edges (:vertices o)) + 060      :kind :path
- 070      :else + 061      ;; `concat` rather than `conj` because order matters.
- - 071      (throw (IllegalArgumentException. -
- - 072               "Not a path or sequence of vertices!")))) + + 062      :vertices (concat (:vertices o) (list (first (:vertices o))))))
- 073   + 063  
- 074  (defn length + 064  (defn path->edges
- 075    "Return the length of this path, in metres. **Note that** + 065    "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of
- 076    1. This is not the same as the distance from the start to the end of the + 066    edges representing that path, polygon or sequence. +
+ + 067  
- 077    path, which, except for absolutely straight paths, will be shorter; + 068    Throws `IllegalArgumentException` if `o` is not a path, a polygon, or
- 078    2. It is not even quite the same as the length of the path *as rendered*, + 069    sequence of vertices." +
+ + 070    [o] +
+ + 071    (cond +
+ + 072      (seq? o) (when +
+ + 073                 (and +
+ + 074                   (vertex? (first o)) +
+ + 075                   (vertex? (first (rest o)))) +
+ + 076                 (cons
- 079    since paths will generally be rendered as spline curves." + 077                   ;; TODO: think about: when constructing an edge from a path, should the
- 080    [path] + 078                   ;; constructed edge be tagged with the tags of the path?
- - 081    (if + + 079                   (e/edge (first o) (first (rest o)))
- - 082      (path? path) + + 080                   (path->edges (rest o))))
- - 083      (reduce + (map e/length (path->edges path))) + + 081      (path? o) (path->edges (:vertices o))
- - 084      (throw (IllegalArgumentException. "Not a path!")))) + + 082      (polygon? o) (path->edges (polygon->path o)) +
+ + 083      :else +
+ + 084      (throw (IllegalArgumentException. +
+ + 085               "Not a path or sequence of vertices!")))) +
+ + 086   +
+ + 087  (defn length +
+ + 088    "Return the length of this path, in metres. **Note that** +
+ + 089    1. This is not the same as the distance from the start to the end of the +
+ + 090    path, which, except for absolutely straight paths, will be shorter; +
+ + 091    2. It is not even quite the same as the length of the path *as rendered*, +
+ + 092    since paths will generally be rendered as spline curves." +
+ + 093    [path] +
+ + 094    (reduce + (map e/length (path->edges (check-path path)))))
diff --git a/docs/cloverage/walkmap/polygon.clj.html b/docs/cloverage/walkmap/polygon.clj.html index 9eae563..e8f3084 100644 --- a/docs/cloverage/walkmap/polygon.clj.html +++ b/docs/cloverage/walkmap/polygon.clj.html @@ -20,10 +20,10 @@ 005              [walkmap.tag :as t]

- 006              [walkmap.utils :refer [kind-type]] + 006              [walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]]
- 007              [walkmap.vertex :refer [vertex vertex?]])) + 007              [walkmap.vertex :refer [check-vertices vertex vertex?]]))
008   @@ -49,7 +49,7 @@ 015      [v (:vertices o)]
- + 016      (and
@@ -70,200 +70,230 @@ 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] +
- 023  (defn triangle? + 028    `(check-kind-type ~o polygon? :polygon)) +
+ + 029   +
+ + 030  (defmacro check-polygons
- 024    "True if `o` satisfies the conditions for a triangle. A triangle shall be a + 031    "If `o` is not a sequence of polygons, throw an `IllegalArgumentException` with an
- 025    polygon with exactly three vertices." + 032    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
- 026    [o] + 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]
- 027    (and + 041    (and
- 028      (coll? o) + 042      (coll? o)
- 029      (= (count (:vertices o)) 3))) + 043      (= (count (:vertices o)) 3)))
- 030   + 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]
- 031  (defn polygon -
- - 032    "Return a polygon constructed from these `vertices`." -
- - 033    [vertices] -
- - 034    (when-not (every? vertex? vertices) -
- - 035      (throw (IllegalArgumentException. -
- - 036               (str -
- - 037                 "Each item on vertices must be a vertex: " -
- - 038                 (s/join " " (map kind-type (remove vertex? vertices))))))) -
- - 039    {:vertices vertices :walkmap.id/id (keyword (gensym "poly")) :kind :polygon}) + 050    `(check-kind-type ~o triangle? :triangle))
- 040   + 051  
- 041  (defn gradient + 052  (defn polygon
- 042    "Return a polygon like `triangle` but with a key `:gradient` whose value is a + 053    "Return a polygon constructed from these `vertices`."
- 043    unit vector representing the gradient across `triangle`." + 054    [& vertices] +
+ + 055    {:vertices (check-vertices vertices) +
+ + 056     :walkmap.id/id (keyword (gensym "poly"))
- 044    [triangle] + 057     :kind :polygon})
- - 045    (when-not (triangle? triangle) + + 058  
- - 046      (throw (IllegalArgumentException. + + 059  (defn gradient
- - 047               (s/join " " ["Must be a triangle:" (kind-type triangle)])))) + + 060    "Return a polygon like `triangle` but with a key `:gradient` whose value is a
- - 048    (let [order (sort #(max (:z %1) (:z %2)) (:vertices triangle)) + + 061    unit vector representing the gradient across `triangle`." +
+ + 062    [triangle] +
+ + 063    (let [order (sort #(max (:z %1) (:z %2)) +
+ + 064                      (:vertices (check-triangle triangle)))
- 049          highest (first order) + 065          highest (first order)
- 050          lowest (last order)] + 066          lowest (last order)]
- 051       (assoc triangle :gradient (e/unit-vector (e/edge lowest highest))))) + 067       (assoc triangle :gradient (e/unit-vector (e/edge lowest highest)))))
- 052   + 068  
- 053  (defn triangle-centre + 069  (defn triangle-centre +
+ + 070    "Return a canonicalised `facet` (i.e. a triangular polygon) with an added
- 054    "Return a canonicalised `facet` (i.e. a triangular polygon) with an added + 071    key `:centre` whose value represents the centre of this facet in 3
- 055    key `:centre` whose value represents the centre of this facet in 3 + 072    dimensions. This only works for triangles, so is here not in
- 056    dimensions. This only works for triangles, so is here not in + 073    `walkmap.polygon`. It is an error (although no exception is currently
- 057    `walkmap.polygon`. It is an error (although no exception is currently + 074    thrown) if the object past is not a triangular polygon."
- 058    thrown) if the object past is not a triangular polygon." + 075    [facet]
- - 059    [facet] -
- - 060    (when-not (triangle? facet) -
- - 061      (throw (IllegalArgumentException. -
- - 062               (s/join " " ["Must be a triangle:" (kind-type facet)])))) -
- - 063    (let [vs (:vertices facet) + + 076    (let [vs (:vertices (check-triangle facet))
- 064          v1 (first vs) + 077          v1 (first vs)
- 065          opposite (e/edge (nth vs 1) (nth vs 2)) + 078          opposite (e/edge (nth vs 1) (nth vs 2))
- 066          oc (e/centre opposite)] + 079          oc (e/centre opposite)]
- 067        (assoc + 080        (assoc
- 068        facet + 081        facet
- 069        :centre + 082        :centre
- 070        (vertex + 083        (vertex
- 071          (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3)) + 084          (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3))
- 072          (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3)) + 085          (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3))
- 073          (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3)))))) -
- - 074   -
- - 075  (defn centre -
- - 076    [poly] -
- - 077    (when-not (polygon? poly) -
- - 078          (throw (IllegalArgumentException. -
- - 079               (s/join " " ["Must be a polygon:" (kind-type poly)])))) -
- - 080    (case (count (:vertices poly)) -
- - 081      3 (triangle-centre poly) -
- - 082      ;; else -
- - 083      (throw -
- - 084        (UnsupportedOperationException. -
- - 085          "The general case of centre for polygons is not yet implemented.")))) -
- - 086   + 086          (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3))))))
087  
+ + 088  (defn centre +
+ + 089    [poly] +
+ + 090    (case (count (:vertices (check-polygon poly))) +
+ + 091      3 (triangle-centre poly) +
+ + 092      ;; else +
+ + 093      (throw +
+ + 094        (UnsupportedOperationException. +
+ + 095          "The general case of centre for polygons is not yet implemented.")))) +
+ + 096   +
+ + 097   +
diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html index 5240aa8..ff71dd2 100644 --- a/docs/cloverage/walkmap/stl.clj.html +++ b/docs/cloverage/walkmap/stl.clj.html @@ -550,7 +550,7 @@ 182    ([filename stl solidname]
- + 183     (l/debug "Solid name is " solidname)
diff --git a/docs/cloverage/walkmap/superstructure.clj.html b/docs/cloverage/walkmap/superstructure.clj.html index a83ad6d..229b2b9 100644 --- a/docs/cloverage/walkmap/superstructure.clj.html +++ b/docs/cloverage/walkmap/superstructure.clj.html @@ -355,7 +355,7 @@ 117    ([o s]
- + 118     (l/debug "Finding objects in:" o)
diff --git a/docs/cloverage/walkmap/svg.clj.html b/docs/cloverage/walkmap/svg.clj.html index c04a8b4..29fe91d 100644 --- a/docs/cloverage/walkmap/svg.clj.html +++ b/docs/cloverage/walkmap/svg.clj.html @@ -274,7 +274,7 @@ 090                   (:facets stl)))]
- + 091      (l/info "Generating SVG for " *preferred-svg-render* " renderer")
@@ -316,7 +316,7 @@ 104     (let [s (binary-stl-file->svg in-filename)]
- + 105       (l/info "Emitting SVG with " *preferred-svg-render* " renderer")
diff --git a/docs/cloverage/walkmap/tag.clj.html b/docs/cloverage/walkmap/tag.clj.html index 32a0cd8..dddaf4e 100644 --- a/docs/cloverage/walkmap/tag.clj.html +++ b/docs/cloverage/walkmap/tag.clj.html @@ -118,7 +118,7 @@ 038    [object & tags]
- + 039    (l/debug "Tagging" (kind-type object) "with" tags)
diff --git a/docs/cloverage/walkmap/utils.clj.html b/docs/cloverage/walkmap/utils.clj.html index 8a5a84d..217b039 100644 --- a/docs/cloverage/walkmap/utils.clj.html +++ b/docs/cloverage/walkmap/utils.clj.html @@ -11,127 +11,301 @@ 002    "Miscellaneous utility functions."
- 003    (:require [clojure.math.numeric-tower :as m])) + 003    (:require [clojure.math.numeric-tower :as m] +
+ + 004              [clojure.string :as s]))
- 004   + 005  
- 005  (defn deep-merge + 006  (defn deep-merge
- 006    "Recursively merges maps. If vals are not maps, the last value wins." + 007    "Recursively merges maps. If vals are not maps, the last value wins."
- 007    ;; TODO: not my implementation, not sure I entirely trust it. + 008    ;; TODO: not my implementation, not sure I entirely trust it.
- 008    ;; TODO TODO: if we are to successfully merge walkmap objects, we must + 009    ;; TODO TODO: if we are to successfully merge walkmap objects, we must
- 009    ;; return, on each object, the union of its tags if any. + 010    ;; return, on each object, the union of its tags if any.
- 010    [& vals] + 011    [& vals]
- 011    (if (every? map? vals) + 012    (if (every? map? vals)
- 012      (apply merge-with deep-merge vals) + 013      (apply merge-with deep-merge vals)
- 013      (last vals))) + 014      (last vals)))
- 014   + 015  
- 015  (defn truncate + 016  (defn truncate
- 016    "If string `s` is more than `n` characters long, return the first `n` + 017    "If string `s` is more than `n` characters long, return the first `n`
- 017    characters; otherwise, return `s`." + 018    characters; otherwise, return `s`."
- 018    [s n] + 019    [s n]
- 019    (if (and (string? s) (number? n) (> (count s) n)) + 020    (if (and (string? s) (number? n) (> (count s) n))
- 020      (subs s 0 n) + 021      (subs s 0 n)
- 021      s)) + 022      s))
- 022   + 023  
- 023  (defn kind-type + 024  (defn kind-type
- 024    "Identify the type of an `object`, e.g. for logging. If it has a `:kind` key, + 025    "Identify the type of an `object`, e.g. for logging. If it has a `:kind` key,
- 025    it's one of ours, and that's what we want. Otherwise, we want its type; but + 026    it's one of ours, and that's what we want. Otherwise, we want its type; but
- 026    the type of `nil` is `nil`, which doesn't get printed when assembling error + 027    the type of `nil` is `nil`, which doesn't get printed when assembling error
- 027    ,essages, so return \"nil\"." + 028    ,essages, so return \"nil\"."
- 028    [object] + 029    [object]
- 029    (or (:kind object) (type object) "nil")) + 030    (or (:kind object) (type object) "nil"))
- 030   + 031  
- 031  (defn =ish + 032  (defn =ish
- 032    "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to + 033    "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to
- 033    within `tolerance` (defaults to one part in a million)." + 034    within `tolerance` (defaults to one part in a million)."
- 034    ([n1 n2] + 035    ([n1 n2]
- 035     (if (and (number? n1) (number? n2)) + 036     (if (and (number? n1) (number? n2))
- 036       (let [m (m/abs (min n1 n2)) + 037       (let [m (m/abs (min n1 n2))
- 037             t (if (zero? m) 0.000001 (* 0.000001 m))] + 038             t (if (zero? m) 0.000001 (* 0.000001 m))]
- 038         (=ish n1 n2 t)) + 039         (=ish n1 n2 t))
- 039       (= n1 n2))) + 040       (= n1 n2)))
- 040    ([n1 n2 tolerance] + 041    ([n1 n2 tolerance]
- 041     (if (and (number? n1) (number? n2)) + 042     (if (and (number? n1) (number? n2))
- 042       (< (m/abs (- n1 n2)) tolerance) + 043       (< (m/abs (- n1 n2)) tolerance)
- 043       (= n1 n2)))) + 044       (= n1 n2)))) +
+ + 045   +
+ + 046  (defmacro check-kind-type +
+ + 047    "If `object` is not of kind-type `expected`, throws an +
+ + 048    IllegalArgumentException with an appropriate message; otherwise, returns +
+ + 049    `object`. If `checkfn` is supplied, it should be a function which tests +
+ + 050    whether the object is of the expected kind-type. +
+ + 051   +
+ + 052    Macro, so that the exception is thrown from the calling function." +
+ + 053    ([object expected] +
+ + 054     `(if-not (= (kind-type ~object) ~expected) +
+ + 055        (throw +
+ + 056          (IllegalArgumentException. +
+ + 057            (s/join +
+ + 058              " " +
+ + 059              ["Expected" ~expected "but found" (kind-type ~object)]))) +
+ + 060        ~object)) +
+ + 061    ([object checkfn expected] +
+ + 062     `(if-not (~checkfn ~object) +
+ + 063        (throw +
+ + 064          (IllegalArgumentException. +
+ + 065            (s/join +
+ + 066              " " +
+ + 067              ["Expected" ~expected "but found" (kind-type ~object)]))) +
+ + 068        ~object))) +
+ + 069   +
+ + 070  (defmacro check-kind-type-seq +
+ + 071    "If some item on sequence `s` is not of the `expected` kind-type, throws an +
+ + 072    IllegalArgumentException with an appropriate message; otherwise, returns +
+ + 073    `object`. If `checkfn` is supplied, it should be a function which tests +
+ + 074    whether the object is of the expected kind-type. +
+ + 075   +
+ + 076    Macro, so that the exception is thrown from the calling function." +
+ + 077    ([s expected] +
+ + 078    `(if-not (every? #(= (kind-type %) ~expected) ~s) +
+ + 079       (throw +
+ + 080         (IllegalArgumentException. +
+ + 081           (s/join +
+ + 082             " " +
+ + 083             ["Expected sequence of" +
+ + 084              ~expected +
+ + 085              "but found (" +
+ + 086              (s/join ", " (remove #(= ~expected %) (map kind-type ~s))) +
+ + 087              ")"]))) +
+ + 088       ~s)) +
+ + 089    ([s checkfn expected] +
+ + 090    `(if-not (every? #(~checkfn %) ~s) +
+ + 091       (throw +
+ + 092         (IllegalArgumentException. +
+ + 093           (s/join +
+ + 094             " " +
+ + 095             ["Expected sequence of" +
+ + 096              ~expected +
+ + 097              "but found (" +
+ + 098              (s/join ", " (remove #(= ~expected %) (map kind-type ~s))) +
+ + 099              ")"]))) +
+ + 100       ~s))) +
+ + 101  
diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html index 2857af8..27eac17 100644 --- a/docs/cloverage/walkmap/vertex.clj.html +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -29,7 +29,7 @@ 008              [taoensso.timbre :as l]

- 009              [walkmap.utils :refer [=ish kind-type truncate]])) + 009              [walkmap.utils :refer [=ish check-kind-type check-kind-type-seq kind-type truncate]]))
010   @@ -139,7 +139,7 @@ 045      (:walkmap.id/id o)
- + 046      (number? (:x o))
@@ -154,299 +154,302 @@ 050  
+ + 051  (defmacro check-vertex +
+ + 052    "If `o` is not a vertex, throw an `IllegalArgumentException` with an +
+ + 053    appropriate message; otherwise, returns `o`. Macro, so exception is thrown +
+ + 054    from the calling function." +
+ + 055    [o] +
- 051  (defn vertex= -
- - 052    "True if vertices `v1`, `v2` represent the same vertex." -
- - 053    [v1 v2] -
- - 054    (every? -
- - 055      #(=ish (% v1) (% v2)) -
- - 056      [:x :y :z])) + 056    `(check-kind-type ~o vertex? :vertex))
057  
- - 058  (defn vertex* + + 058  (defmacro check-vertices
- 059    "Return a vertex like `v1`, but with each of its coordinates multiplied + 059    "If `o` is not a sequence of vertices, throw an `IllegalArgumentException` with an
- 060    by the equivalent vertex in `v2`." + 060    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
- 061    [v1 v2] + 061    from the calling function." +
+ + 062    [o]
- 062    (if -
- - 063      (and (vertex? v1) (vertex? v2)) -
- - 064      (let [f (fn [v1 v2 coord] -
- - 065                (* (or (coord v1) 0) -
- - 066                   ;; one here is deliberate! -
- - 067                   (or (coord v2) 1)))] -
- - 068        (assoc v1 :x (f v1 v2 :x) -
- - 069          :y (f v1 v2 :y) -
- - 070          :z (f v1 v2 :z))) -
- - 071      (do (l/warn -
- - 072            (s/join -
- - 073              " " -
- - 074              ["in `vertex-multiply`, both must be vectors. v1:" v1 "v2:" v2])) -
- - 075        v1))) + 063    `(check-kind-type-seq ~o vertex? :vertex))
- 076   + 064  
- 077  (defn vertex + 065  (defn vertex=
- 078    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map + 066    "True if vertices `v1`, `v2` represent the same vertex."
- 079    with those values, plus a unique `:walkmap.id/id` value, and `:kind` set to `:vertex`. + 067    [v1 v2]
- - 080    It's not necessary to use this function to create a vertex, but the `:walkmap.id/id` + + 068    (check-vertex v1)
- - 081    must be present and must be unique." + + 069    (check-vertex v2)
- - 082    ([x y] + + 070    (every?
- 083     (let [v {:x x :y y :kind :vertex}] + 071      #(=ish (% v1) (% v2))
- - 084       (assoc v :walkmap.id/id (vertex-key v)))) + + 072      [:x :y :z])) +
+ + 073   +
+ + 074  (defn vertex*
- 085    ([x y z] + 075    "Return a vertex like `v1`, but with each of its coordinates multiplied
- - 086     (let [v (assoc (vertex x y) :z z)] + + 076    by the equivalent vertex in `v2`. It is an error, and an exception will
- - 087       (assoc v :walkmap.id/id (vertex-key v))))) + + 077    be thrown, if either `v1` or `v2` is not a vertex." +
+ + 078    [v1 v2] +
+ + 079    (check-vertex v1) +
+ + 080    (check-vertex v2) +
+ + 081    (let [f (fn [v1 v2 coord] +
+ + 082              (* (or (coord v1) 0) +
+ + 083                 ;; one here is deliberate! +
+ + 084                 (or (coord v2) 1)))] +
+ + 085      (assoc v1 :x (f v1 v2 :x) +
+ + 086        :y (f v1 v2 :y) +
+ + 087        :z (f v1 v2 :z))))
088  
- 089  (defn canonicalise + 089  (defn vertex
- 090    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, + 090    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
- 091    upgrade it to something we will recognise as a vertex." + 091    with those values, plus a unique `:walkmap.id/id` value, and `:kind` set to `:vertex`.
- 092    [o] + 092    It's not necessary to use this function to create a vertex, but the `:walkmap.id/id` +
+ + 093    must be present and must be unique." +
+ + 094    ([x y] +
+ + 095     (let [v {:x x :y y :kind :vertex}] +
+ + 096       (assoc v :walkmap.id/id (vertex-key v)))) +
+ + 097    ([x y z] +
+ + 098     (let [v {:x x :y y :z z :kind :vertex}] +
+ + 099       (assoc v :walkmap.id/id (vertex-key v))))) +
+ + 100  
- 093    (if + 101  (defn canonicalise +
+ + 102    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, +
+ + 103    upgrade it to something we will recognise as a vertex." +
+ + 104    [o] +
+ + 105    (if
- 094      (and + 106      (and
- 095        (map? o) + 107        (map? o)
- 096        (number? (:x o)) + 108        (number? (:x o))
- 097        (number? (:y o)) + 109        (number? (:y o))
- 098        (or (nil? (:z o)) (number? (:z o)))) + 110        (or (nil? (:z o)) (number? (:z o))))
- 099      (assoc o :kind :vertex :walkmap.id/id (vertex-key o)) + 111      (assoc o :kind :vertex :walkmap.id/id (vertex-key o))
- 100      (throw + 112      (throw
- 101        (IllegalArgumentException. + 113        (IllegalArgumentException.
- 102          (truncate + 114          (truncate
- 103            (str "Not a proto-vertex: must have numeric `:x` and `:y`: " + 115            (str "Not a proto-vertex: must have numeric `:x` and `:y`: "
- 104                 (or o "nil")) + 116                 (or o "nil"))
- 105            80))))) + 117            80)))))
- 106   + 118  
- 107  (def ensure3d + 119  (def ensure3d
- 108    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise + 120    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
- 109    return a vertex like `o` but having thie `dflt` value as the value of its + 121    return a vertex like `o` but having this `dflt` value as the value of its
- 110    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. + 122    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified.
- 111   + 123  
- 112    If `o` is not a vertex, throws an exception." + 124    If `o` is not a vertex, throws an exception."
- 113    (memoize + 125    (memoize
- 114      (fn + 126      (fn
- 115        ([o] + 127        ([o]
- 116         (ensure3d o 0.0)) + 128         (ensure3d o 0.0))
- 117        ([o dflt] + 129        ([o dflt]
- - 118         (cond + + 130         (if (:z (check-vertex o))
- - 119           (not (vertex? o)) (throw -
- - 120                               (IllegalArgumentException. -
- - 121                                 (truncate (str "Not a vertex: " (or o "nil")) 80))) -
- - 122           (:z o) o + + 131           o
- 123           :else (assoc o :z dflt)))))) + 132           (assoc o :z dflt))))))
- 124   + 133  
- 125  (def ensure2d + 134  (def ensure2d
- 126    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." + 135    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
- 127    (memoize + 136    (memoize
- 128      (fn [o] + 137      (fn [o]
- - 129        (if -
- - 130          (vertex? o) -
- - 131          (assoc o :z 0.0) -
- - 132          (throw -
- - 133            (IllegalArgumentException. -
- - 134              (truncate (str "Not a vertex: " (or o "nil")) 80))))))) + + 138        (assoc (check-vertex o) :z 0.0))))
- 135   + 139  
- 136  (defn within-box? + 140  (defn within-box?
- 137    "True if `target` is within the box defined by `minv` and `maxv`. All + 141    "True if `target` is within the box defined by `minv` and `maxv`. All
- 138    arguments must be vertices; additionally, both `minv` and `maxv` must + 142    arguments must be vertices; additionally, both `minv` and `maxv` must
- 139    have `:z` coordinates." + 143    have `:z` coordinates."
- 140    [target minv maxv] + 144    [target minv maxv]
- - 141    (when-not (and (vertex? target) (vertex? minv) (vertex? maxv)) + + 145    (check-vertices [target minv maxv])
- 142      (throw (IllegalArgumentException. -
- - 143               (s/join " " ["Arguments to `within-box?` must be vertices:" -
- - 144                            (map kind-type [target minv maxv])])))) -
- - 145    (every? + 146    (every?
- 146      (map + 147      (map
- 147        #(< (% minv) (or (% target) 0) (% maxv)) + 148        #(< (% minv) (or (% target) 0) (% maxv))
- 148        [:x :y :z]))) + 149        [:x :y :z])))
diff --git a/docs/codox/dali-performance.html b/docs/codox/dali-performance.html index 6d81232..6087c9b 100644 --- a/docs/codox/dali-performance.html +++ b/docs/codox/dali-performance.html @@ -1,6 +1,6 @@ -Dali performance

Dali performance

+Dali performance

Dali performance

Notes written while trying to characterise the performance problem in Dali.

Hypothesis one: it’s the way I format the polygons that’s the issue

Firstly, with both versions of stl->svg using the same version of facet->svg-poly, i.e. this one:

diff --git a/docs/codox/index.html b/docs/codox/index.html index b9532b2..faeaa60 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Walkmap 0.1.0-SNAPSHOT

Walkmap 0.1.0-SNAPSHOT

Released under the EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0

A Clojure library designed to assist in computing walkmaps for games.

Installation

To install, add the following dependency to your project or build file:

[journeyman-cc/walkmap "0.1.0-SNAPSHOT"]

Topics

Namespaces

walkmap.core

This namespace mostly gets used as a scratchpad for ideas which haven’t yet solidified.

Public variables and functions:

    walkmap.core

    This namespace mostly gets used as a scratchpad for ideas which haven’t yet solidified.

    Public variables and functions:

      walkmap.edge

      Essentially the specification for things we shall consider to be an edge. An edge is a line segment having just a start and an end, with no intervening nodes.

      walkmap.geometry

      TODO: write docs

      Public variables and functions:

        walkmap.ocean

        Deal with (specifically, at this stage, cull) ocean areas

        Public variables and functions:

        walkmap.path

        Essentially the specification for things we shall consider to be path. Note that for these purposes path means any continuous linear feature, where such features specifically include watercourses.

        Public variables and functions:

        walkmap.polygon

        Essentially the specification for things we shall consider to be polygons.

        Public variables and functions:

        walkmap.stl

        Utility functions dealing with stereolithography (STL) files. Not a stable API yet!

        walkmap.superstructure

        single indexing structure for walkmap objects

        Public variables and functions:

        walkmap.svg

        Utility functions for writing stereolithography (STL) files (and possibly, later, other geometry files of interest to us) as scalable vector graphics (SVG).

        walkmap.tag

        Code for tagging, untagging, and finding tags on objects. Note the use of the namespaced keyword, :walkmap.tag/tags, denoted in this file ::tags. This is in an attempt to avoid name clashes with other uses of this key.

        Public variables and functions:

        walkmap.utils

        Miscellaneous utility functions.

        Public variables and functions:

        walkmap.vertex

        Essentially the specification for things we shall consider to be vertices.

        Public variables and functions:

        \ No newline at end of file +Walkmap 0.1.0-SNAPSHOT

        Walkmap 0.1.0-SNAPSHOT

        Released under the EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0

        A Clojure library designed to assist in computing walkmaps for games.

        Installation

        To install, add the following dependency to your project or build file:

        [journeyman-cc/walkmap "0.1.0-SNAPSHOT"]

        Topics

        Namespaces

        walkmap.edge

        Essentially the specification for things we shall consider to be an edge. An edge is a line segment having just a start and an end, with no intervening nodes.

        walkmap.id

        The namespace within which the privileged keyword :walkmap.id/id is defined.

        Public variables and functions:

        walkmap.ocean

        Deal with (specifically, at this stage, cull) ocean areas

        Public variables and functions:

        walkmap.path

        Essentially the specification for things we shall consider to be path. Note that for these purposes path means any continuous linear feature, where such features specifically include watercourses.

        walkmap.polygon

        Essentially the specification for things we shall consider to be polygons.

        walkmap.read-svg

        Utility functions for scalable vector graphics (SVG) into walkmap structures.

        walkmap.routing

        Finding optimal routes to traverse a map.

        Public variables and functions:

          walkmap.stl

          Utility functions dealing with stereolithography (STL) files. Not a stable API yet!

          walkmap.svg

          Utility functions for writing stereolithography (STL) files (and possibly, later, other geometry files of interest to us) as scalable vector graphics (SVG).

          walkmap.tag

          Code for tagging, untagging, and finding tags on objects. Note the use of the namespaced keyword, :walkmap.tag/tags, denoted in this file ::tags. This is in an attempt to avoid name clashes with other uses of this key.

          Public variables and functions:

          walkmap.utils

          Miscellaneous utility functions.

          walkmap.vertex

          Essentially the specification for things we shall consider to be vertices.

          \ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index b999120..8eadcd4 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to walkmap

          Introduction to walkmap

          +Introduction to walkmap

          Introduction to walkmap

          This library is written in support of work on The Great Game, but is separate because it may be of some use in other settings.

          Usage

          What works:

          diff --git a/docs/codox/walkmap.edge.html b/docs/codox/walkmap.edge.html index 7e9067b..5dd5e9e 100644 --- a/docs/codox/walkmap.edge.html +++ b/docs/codox/walkmap.edge.html @@ -1,10 +1,10 @@ -walkmap.edge documentation

          walkmap.edge

          Essentially the specification for things we shall consider to be an edge. An edge is a line segment having just a start and an end, with no intervening nodes.

          centre

          (centre edge)

          Return the vertex that represents the centre of this edge.

          collinear2d?

          (collinear2d? e1 e2)

          True if the projections of edges e1, e2 onto the x, y plane are collinear.

          collinear?

          (collinear? e1 e2)

          True if edges e1 and e2 are collinear with one another.

          edge

          (edge v1 v2)

          Return an edge between vertices v1 and v2.

          edge?

          (edge? o)

          True if o satisfies the conditions for a edge. An edge shall be a map having the keys :start and :end, such that the values of each of those keys shall be a vertex.

          intersection2d

          (intersection2d e1 e2)

          The probability of two lines intersecting in 3d space is low, and actually that is mostly not something we’re interested in. We’re interested in intersection in the x,y plane. This function returns a vertex representing a point vertically over the intersection of edges e1, e2 in the x,y plane, whose z coordinate is

          +walkmap.edge documentation

          walkmap.edge

          Essentially the specification for things we shall consider to be an edge. An edge is a line segment having just a start and an end, with no intervening nodes.

          centre

          (centre edge)

          Return the vertex that represents the centre of this edge.

          collinear2d?

          (collinear2d? e1 e2)

          True if the projections of edges e1, e2 onto the x, y plane are collinear.

          collinear?

          (collinear? e1 e2)

          True if edges e1 and e2 are collinear with one another.

          edge

          (edge v1 v2)

          Return an edge between vertices v1 and v2.

          edge?

          (edge? o)

          True if o satisfies the conditions for a edge. An edge shall be a map having the keys :start and :end, such that the values of each of those keys shall be a vertex.

          intersection2d

          (intersection2d e1 e2)

          The probability of two lines intersecting in 3d space is low, and actually that is mostly not something we’re interested in. We’re interested in intersection in the x,y plane. This function returns a vertex representing a point vertically over the intersection of edges e1, e2 in the x,y plane, whose z coordinate is

          • 0 if both edges are 2d (i.e. have missing or zero z coordinates);
          • if one edge is 2d, then the point on the other edge over the intersection;
          • otherwise, the average of the z coordinates of the points on the two edges over the intersection.

          If no such intersection exists, nil is returned.

          -

          It is an error, and an exception will be thrown, if either e1 or e2 is not an edge.

          length

          (length e)

          Return the length of the edge e.

          minimaxd

          (minimaxd edge coord f)

          Apply function f to coord of the vertices at start and end of edge and return the result. Intended use case is f = min or max, coord is :x, :y or :z. No checks are made for sane arguments.

          on2d?

          (on2d? e v)

          True if vertex v is on edge e when projected onto the x, y plane.

          on?

          (on? e v)

          True if the vertex v is on the edge e.

          orientation

          (orientation p q r)

          Determine whether the ordered sequence of vertices p, q and r run clockwise, collinear or anticlockwise in the x,y plane.

          overlaps2d?

          (overlaps2d? e1 e2)

          True if the recangle in the x,y plane bisected by edge e1 overlaps that bisected by edge e2. It is an error if either e1 or e2 is not an edge.

          parallel?

          (parallel? & edges)

          True if all edges passed are parallel with one another.

          unit-vector

          (unit-vector e)

          Return an vertex parallel to e starting from the coordinate origin. Two edges which are parallel will have the same unit vector.

          \ No newline at end of file +

          It is an error, and an exception will be thrown, if either e1 or e2 is not an edge.

          length

          (length e)

          Return the length of the edge e.

          minimaxd

          (minimaxd edge coord f)

          Apply function f to coord of the vertices at start and end of edge and return the result. Intended use case is f = min or max, coord is :x, :y or :z. No checks are made for sane arguments.

          on2d?

          (on2d? e v)

          True if vertex v is on edge e when projected onto the x, y plane.

          on?

          (on? e v)

          True if the vertex v is on the edge e.

          overlaps2d?

          (overlaps2d? e1 e2)

          True if the recangle in the x,y plane bisected by edge e1 overlaps that bisected by edge e2. It is an error if either e1 or e2 is not an edge.

          parallel?

          (parallel? & edges)

          True if all edges passed are parallel with one another.

          unit-vector

          (unit-vector e)

          Return an vertex parallel to e starting from the coordinate origin. Two edges which are parallel will have the same unit vector.

          \ No newline at end of file diff --git a/docs/codox/walkmap.id.html b/docs/codox/walkmap.id.html new file mode 100644 index 0000000..cd607e5 --- /dev/null +++ b/docs/codox/walkmap.id.html @@ -0,0 +1,3 @@ + +walkmap.id documentation

          walkmap.id

          The namespace within which the privileged keyword :walkmap.id/id is defined.

          id

          The magic id key walkmap uses, to distinguish it from all other uses of the unprotected keyword.

          \ No newline at end of file diff --git a/docs/codox/walkmap.ocean.html b/docs/codox/walkmap.ocean.html index d449226..f59e609 100644 --- a/docs/codox/walkmap.ocean.html +++ b/docs/codox/walkmap.ocean.html @@ -1,4 +1,4 @@ -walkmap.ocean documentation

          walkmap.ocean

          Deal with (specifically, at this stage, cull) ocean areas

          *sea-level*

          dynamic

          The sea level on heightmaps we’re currently handling. If characters are to be able to swin in the sea, we must model the sea bottom, so we need heightmaps which cover at least the continental shelf. However, the sea bottom is not walkable territory and can be culled from walkmaps.

          -

          Note must be a floating point number. (= 0 0.0) returns false!

          cull-ocean-facets

          (cull-ocean-facets stl)

          Ye cannae walk on water. Remove all facets from this stl structure which are at sea level.

          ocean?

          (ocean? facet)

          Of a facet, is the altitude of every vertice equal to *sea-level*?

          \ No newline at end of file +walkmap.ocean documentation

          walkmap.ocean

          Deal with (specifically, at this stage, cull) ocean areas

          *sea-level*

          dynamic

          The sea level on heightmaps we’re currently handling. If characters are to be able to swin in the sea, we must model the sea bottom, so we need heightmaps which cover at least the continental shelf. However, the sea bottom is not walkable territory and can be culled from walkmaps.

          +

          Note must be a floating point number. (= 0 0.0) returns false!

          cull-ocean-facets

          (cull-ocean-facets stl)

          Ye cannae walk on water. Remove all facets from this stl structure which are at sea level.

          ocean?

          (ocean? facet)

          Of a facet, is the altitude of every vertice equal to *sea-level*?

          \ No newline at end of file diff --git a/docs/codox/walkmap.path.html b/docs/codox/walkmap.path.html index b86382d..1c18dac 100644 --- a/docs/codox/walkmap.path.html +++ b/docs/codox/walkmap.path.html @@ -1,5 +1,5 @@ -walkmap.path documentation

          walkmap.path

          Essentially the specification for things we shall consider to be path. Note that for these purposes path means any continuous linear feature, where such features specifically include watercourses.

          length

          (length path)

          Return the length of this path, in metres. Note that 1. This is not the same as the distance from the start to the end of the path, which, except for absolutely straight paths, will be shorter; 2. It is not even quite the same as the length of the path as rendered, since paths will generally be rendered as spline curves.

          path

          (path & vertices)

          Return a path constructed from these vertices.

          path->edges

          (path->edges o)

          if o is a path, a polygon, or a sequence of vertices, return a sequence of edges representing that path, polygon or sequence.

          -

          Throws IllegalArgumentException if o is not a path, a polygon, or sequence of vertices.

          path?

          (path? o)

          True if o satisfies the conditions for a path. A path shall be a map having the key :vertices, whose value shall be a sequence of vertices as defined in walkmap.vertex.

          polygon->path

          (polygon->path o)

          If o is a polygon, return an equivalent path. What’s different about a path is that in polygons there is an implicit edge between the first vertex and the last. In paths, there isn’t, so we need to add that edge explicitly.

          -

          If o is not a polygon, will throw an exception.

          \ No newline at end of file +walkmap.path documentation

          walkmap.path

          Essentially the specification for things we shall consider to be path. Note that for these purposes path means any continuous linear feature, where such features specifically include watercourses.

          check-path

          macro

          (check-path o)

          If o is not a path, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          check-paths

          macro

          (check-paths o)

          If o is not a sequence of paths, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          length

          (length path)

          Return the length of this path, in metres. Note that 1. This is not the same as the distance from the start to the end of the path, which, except for absolutely straight paths, will be shorter; 2. It is not even quite the same as the length of the path as rendered, since paths will generally be rendered as spline curves.

          path

          (path & vertices)

          Return a path constructed from these vertices.

          path->edges

          (path->edges o)

          if o is a path, a polygon, or a sequence of vertices, return a sequence of edges representing that path, polygon or sequence.

          +

          Throws IllegalArgumentException if o is not a path, a polygon, or sequence of vertices.

          path?

          (path? o)

          True if o satisfies the conditions for a path. A path shall be a map having the key :vertices, whose value shall be a sequence of vertices as defined in walkmap.vertex.

          polygon->path

          (polygon->path o)

          If o is a polygon, return an equivalent path. What’s different about a path is that in polygons there is an implicit edge between the first vertex and the last. In paths, there isn’t, so we need to add that edge explicitly.

          +

          If o is not a polygon, will throw an exception.

          \ No newline at end of file diff --git a/docs/codox/walkmap.polygon.html b/docs/codox/walkmap.polygon.html index 997efc6..c181388 100644 --- a/docs/codox/walkmap.polygon.html +++ b/docs/codox/walkmap.polygon.html @@ -1,3 +1,3 @@ -walkmap.polygon documentation

          walkmap.polygon

          Essentially the specification for things we shall consider to be polygons.

          polygon?

          (polygon? o)

          True if o satisfies the conditions for a polygon. A polygon shall be a map which has a value for the key :vertices, where that value is a sequence of vertices.

          \ No newline at end of file +walkmap.polygon documentation

          walkmap.polygon

          Essentially the specification for things we shall consider to be polygons.

          centre

          (centre poly)

          TODO: write docs

          check-polygon

          macro

          (check-polygon o)

          If o is not a polygon, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          check-polygons

          macro

          (check-polygons o)

          If o is not a sequence of polygons, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          check-triangle

          macro

          (check-triangle o)

          If o is not a triangle, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          gradient

          (gradient triangle)

          Return a polygon like triangle but with a key :gradient whose value is a unit vector representing the gradient across triangle.

          polygon

          (polygon & vertices)

          Return a polygon constructed from these vertices.

          polygon?

          (polygon? o)

          True if o satisfies the conditions for a polygon. A polygon shall be a map which has a value for the key :vertices, where that value is a sequence of vertices.

          triangle-centre

          (triangle-centre facet)

          Return a canonicalised facet (i.e. a triangular polygon) with an added key :centre whose value represents the centre of this facet in 3 dimensions. This only works for triangles, so is here not in walkmap.polygon. It is an error (although no exception is currently thrown) if the object past is not a triangular polygon.

          triangle?

          (triangle? o)

          True if o satisfies the conditions for a triangle. A triangle shall be a polygon with exactly three vertices.

          \ No newline at end of file diff --git a/docs/codox/walkmap.read-svg.html b/docs/codox/walkmap.read-svg.html new file mode 100644 index 0000000..2a2fb55 --- /dev/null +++ b/docs/codox/walkmap.read-svg.html @@ -0,0 +1,3 @@ + +walkmap.read-svg documentation

          walkmap.read-svg

          Utility functions for scalable vector graphics (SVG) into walkmap structures.

          command-string->vertices

          (command-string->vertices s)

          Return the destination of each successive line (l, L) and move (m, M) command in this string s, expected to be an SVG path command string.

          match->vertex

          (match->vertex match-vector x y)

          TODO: write docs

          path-elt->path

          (path-elt->path elt)

          Given the SVG path element elt, return a walkmap path structure representing the line (l, L) and move (m, M) commands in that path.

          progeny

          (progeny elt predicate)

          Return all the nodes in the XML structure below this elt which match this predicate.

          read-svg

          (read-svg file-name)(read-svg file-name map-kind)

          TODO: write docs

          upper-case?

          (upper-case? s)

          TODO: write docs

          \ No newline at end of file diff --git a/docs/codox/walkmap.routing.html b/docs/codox/walkmap.routing.html new file mode 100644 index 0000000..45e5507 --- /dev/null +++ b/docs/codox/walkmap.routing.html @@ -0,0 +1,3 @@ + +walkmap.routing documentation

          walkmap.routing

          Finding optimal routes to traverse a map.

          \ No newline at end of file diff --git a/docs/codox/walkmap.stl.html b/docs/codox/walkmap.stl.html index d35b6c0..dfe7091 100644 --- a/docs/codox/walkmap.stl.html +++ b/docs/codox/walkmap.stl.html @@ -1,5 +1,8 @@ -walkmap.stl documentation

          walkmap.stl

          Utility functions dealing with stereolithography (STL) files. Not a stable API yet!

          binary-stl

          A codec for binary STL files

          binary-stl-to-ascii

          (binary-stl-to-ascii in-filename)(binary-stl-to-ascii in-filename out-filename)

          Convert the binary STL file indicated by in-filename, and write it to out-filename, if specified; otherwise, to a file with the same basename as in-filename but the extension .ascii.stl.

          canonicalise

          (canonicalise o)(canonicalise o map-kind)

          Objects read in from STL won’t have all the keys/values we need them to have. o may be a map (representing a facet or a vertex), or a sequence of such maps; if it isn’t recognised it is at present just returned unchanged. map-kind, if passed, must be a keyword indicating the value represented by the z axis (defaults to :height). It is an error, and an exception will be thrown, if map-kind is not a keyword.

          centre

          (centre facet)

          Return a canonicalised facet (i.e. a triangular polygon) with an added key :centre whose value represents the centre of this facet in 3 dimensions. This only works for triangles, so is here not in walkmap.polygon. It is an error (although no exception is currently thrown) if the object past is not a triangular polygon.

          decode-binary-stl

          (decode-binary-stl filename)(decode-binary-stl filename map-kind)

          Parse a binary STL file from this filename and return an STL structure representing its contents. map-kind, if passed, must be a keyword indicating the value represented by the z axis (defaults to :height). It is an error, and an exception will be thrown, if map-kind is not a keyword.

          -

          NOTE that we’ve no way of verifying that the input file is binary STL data, if it is not this will run but will return garbage.

          facet

          A codec for a facet (triangle) within a binary STL file.

          stl->ascii

          (stl->ascii stl)(stl->ascii stl solidname)

          Return as a string an ASCII rendering of the stl structure.

          stl?

          (stl? o)(stl? o verify-count?)

          True if o is recogniseable as an STL structure. An STL structure must have a key :facets, whose value must be a sequence of polygons; and may have a key :header whose value should be a string, and/or a key :count, whose value should be a positive integer.

          -

          If verify-count? is passed and is not false, verify that the value of the :count header is equal to the number of facets.

          vect

          A codec for vectors within a binary STL file.

          write-ascii-stl

          (write-ascii-stl filename stl)(write-ascii-stl filename stl solidname)

          Write an stl structure as read by decode-binary-stl to this filename as ASCII encoded STL.

          \ No newline at end of file +walkmap.stl documentation

          walkmap.stl

          Utility functions dealing with stereolithography (STL) files. Not a stable API yet!

          binary-stl

          A codec for binary STL files

          binary-stl-to-ascii

          (binary-stl-to-ascii in-filename)(binary-stl-to-ascii in-filename out-filename)

          Convert the binary STL file indicated by in-filename, and write it to out-filename, if specified; otherwise, to a file with the same basename as in-filename but the extension .ascii.stl.

          canonicalise

          (canonicalise o)(canonicalise o map-kind)(canonicalise o map-kind scale-vertex)

          Objects read in from STL won’t have all the keys/values we need them to have. o may be a map (representing a facet or a vertex), or a sequence of such maps; if it isn’t recognised it is at present just returned unchanged. map-kind, if passed, must be a keyword indicating the value represented by the z axis (defaults to :height). It is an error, and an exception will be thrown, if map-kind is not a keyword.

          decode-binary-stl

          (decode-binary-stl filename)(decode-binary-stl filename map-kind)(decode-binary-stl filename mapkind superstucture)(decode-binary-stl filename map-kind superstructure scale-vertex)

          Parse a binary STL file from this filename and return an STL structure representing its contents. map-kind, if passed, must be a keyword or sequence of keywords indicating the semantic value represented by the z axis (defaults to :height).

          +

          If superstructure is supplied and is a map, the generated STL structure will be stored in that superstructure, which will be returned.

          +

          If scale-vertex is supplied, it must be a three dimensional vertex (i.e. the :z key must have a numeric value) representing the amount by which each of the vertices read from the STL will be scaled.

          +

          It is an error, and an exception will be thrown, if map-kind is not a keyword or sequence of keywords.

          +

          NOTE that we’ve no way of verifying that the input file is binary STL data, if it is not this will run but will return garbage.

          facet

          A codec for a facet (triangle) within a binary STL file.

          stl->ascii

          (stl->ascii stl)(stl->ascii stl solidname)

          Return as a string an ASCII rendering of the stl structure.

          stl?

          (stl? o)(stl? o verify-count?)

          True if o is recogniseable as an STL structure. An STL structure must have a key :facets, whose value must be a sequence of polygons; and may have a key :header whose value should be a string, and/or a key :count, whose value should be a positive integer.

          +

          If verify-count? is passed and is not false, verify that the value of the :count header is equal to the number of facets.

          vect

          A codec for vectors within a binary STL file.

          write-ascii-stl

          (write-ascii-stl filename stl)(write-ascii-stl filename stl solidname)

          Write an stl structure as read by decode-binary-stl to this filename as ASCII encoded STL.

          \ No newline at end of file diff --git a/docs/codox/walkmap.superstructure.html b/docs/codox/walkmap.superstructure.html index 0497927..56b4fec 100644 --- a/docs/codox/walkmap.superstructure.html +++ b/docs/codox/walkmap.superstructure.html @@ -1,19 +1,19 @@ -walkmap.superstructure documentation

          walkmap.superstructure

          single indexing structure for walkmap objects

          add-to-superstructure

          (add-to-superstructure o)(add-to-superstructure s o)

          Return a superstructure like s with object o added. If o is a collection, return a superstructure like s with each element of o added. If only one argument is supplied it will be assumed to represent o and a new superstructure will be returned.

          +walkmap.superstructure documentation

          walkmap.superstructure

          single indexing structure for walkmap objects

          in-retrieve

          (in-retrieve x s)

          Internal guts of retrieve, q.v. x can be anything; s must be a walkmap superstructure. TODO: recursive, quite likely to blow the fragile Clojure stack. Probably better to do this with walk, but I don’t yet understand that.

          in-store-find-objects

          (in-store-find-objects o)(in-store-find-objects o s)

          Return an id -> object map of every object within o. Internal to in-store, q.v. Use at your own peril.

          in-store-replace-with-keys

          (in-store-replace-with-keys o)

          Return a copy of o in which each reified walkmap object within o has been replaced with the :walkmap.id/id of that object. Internal to in-store, q.v. Use at your own peril.

          index-vertex

          (index-vertex s o v)

          Return a superstructure like s in which object o is indexed by vertex v. It is an error (and an exception may be thrown) if

          +
            +
          1. s is not a map;
          2. +
          3. o is not a map;
          4. +
          5. o does not have a value for the key :walkmap.id/id;
          6. +
          7. v is not a vertex.
          8. +

          index-vertices

          (index-vertices s o)

          Return a superstructure like s in which object o is indexed by its vertices. It is an error (and an exception may be thrown) if

          +
            +
          1. s is not a map;
          2. +
          3. o is not a map;
          4. +
          5. o does not have a value for the key :walkmap.id/id.
          6. +

          retrieve

          (retrieve id s)

          Retrieve the canonical representation of the object with this id from the superstructure s.

          search-vertices

          (search-vertices s minv maxv)(search-vertices s minv maxv d2?)

          Search superstructure s for vertices within the box defined by vertices minv and maxv. Every coordinate in minv must have a lower value than the equivalent coordinate in maxv. If d2? is supplied and not false, search only in the x,y projection.

          store

          (store o)(store o s)

          Return a superstructure like s with object o added. If only one argument is supplied it will be assumed to represent o and a new superstructure will be returned.

          It is an error (and an exception may be thrown) if

          1. s is not a map;
          2. -
          3. o is not a map, or a sequence of maps.
          4. -

          index-vertex

          (index-vertex s o v)

          Return a superstructure like s in which object o is indexed by vertex v. It is an error (and an exception may be thrown) if

          -
            -
          1. s is not a map;
          2. -
          3. o is not a map;
          4. -
          5. o does not have a value for the key :id;
          6. -
          7. v is not a vertex.
          8. -

          index-vertices

          (index-vertices s o)

          Return a superstructure like s in which object o is indexed by its vertices. It is an error (and an exception may be thrown) if

          -
            -
          1. s is not a map;
          2. -
          3. o is not a map;
          4. -
          5. o does not have a value for the key :id.
          6. -
          \ No newline at end of file +
        • o is not a recognisable walkmap object
        • +

          vertex-index

          TODO: write docs

          vertices

          (vertices o)

          If o is an object with vertices, return those vertices, else nil.

          \ No newline at end of file diff --git a/docs/codox/walkmap.svg.html b/docs/codox/walkmap.svg.html index 6b8baa2..7b41486 100644 --- a/docs/codox/walkmap.svg.html +++ b/docs/codox/walkmap.svg.html @@ -1,3 +1,3 @@ -walkmap.svg documentation

          walkmap.svg

          Utility functions for writing stereolithography (STL) files (and possibly, later, other geometry files of interest to us) as scalable vector graphics (SVG).

          *preferred-svg-render*

          dynamic

          Mainly for debugging dali; switch SVG renderer to use. Expected values: :dali, :hiccup.

          binary-stl-file->svg

          (binary-stl-file->svg in-filename)(binary-stl-file->svg in-filename out-filename)

          Given only an in-filename, parse the indicated file, expected to be binary STL, and return an equivalent SVG structure. Given both in-filename and out-filename, as side-effect write the SVG to the indicated output file.

          dali-stl->svg

          (dali-stl->svg stl minx maxx miny maxy)

          Format this stl as SVG for the dali renderer on a page with these bounds.

          hiccup-stl->svg

          (hiccup-stl->svg stl minx maxx miny maxy)

          Format this stl as SVG for the hiccup renderer on a page with these bounds.

          stl->svg

          (stl->svg stl)

          Convert this in-memory stl structure, as read by decode-binary-stl, into an in-memory hiccup representation of SVG structure, and return it.

          \ No newline at end of file +walkmap.svg documentation

          walkmap.svg

          Utility functions for writing stereolithography (STL) files (and possibly, later, other geometry files of interest to us) as scalable vector graphics (SVG).

          *preferred-svg-render*

          dynamic

          Mainly for debugging dali; switch SVG renderer to use. Expected values: :dali, :hiccup.

          binary-stl-file->svg

          (binary-stl-file->svg in-filename)(binary-stl-file->svg in-filename out-filename)

          Given only an in-filename, parse the indicated file, expected to be binary STL, and return an equivalent SVG structure. Given both in-filename and out-filename, as side-effect write the SVG to the indicated output file.

          dali-stl->svg

          (dali-stl->svg stl minx maxx miny maxy)

          Format this stl as SVG for the dali renderer on a page with these bounds.

          hiccup-stl->svg

          (hiccup-stl->svg stl minx maxx miny maxy)

          Format this stl as SVG for the hiccup renderer on a page with these bounds.

          stl->svg

          (stl->svg stl)

          Convert this in-memory stl structure, as read by decode-binary-stl, into an in-memory hiccup representation of SVG structure, and return it.

          \ No newline at end of file diff --git a/docs/codox/walkmap.tag.html b/docs/codox/walkmap.tag.html index 4ffe270..26ad5df 100644 --- a/docs/codox/walkmap.tag.html +++ b/docs/codox/walkmap.tag.html @@ -1,15 +1,16 @@ -walkmap.tag documentation

          walkmap.tag

          Code for tagging, untagging, and finding tags on objects. Note the use of the namespaced keyword, :walkmap.tag/tags, denoted in this file ::tags. This is in an attempt to avoid name clashes with other uses of this key.

          tag

          (tag object & tags)

          Return an object like this object but with these tags added to its tags, if they are not already present. It is an error (and an exception will be thrown) if

          +walkmap.tag documentation

          walkmap.tag

          Code for tagging, untagging, and finding tags on objects. Note the use of the namespaced keyword, :walkmap.tag/tags, denoted in this file ::tags. This is in an attempt to avoid name clashes with other uses of this key.

          tag

          (tag object & tags)

          Return an object like this object but with these tags added to its tags, if they are not already present. It is an error (and an exception will be thrown) if

          +
            +
          1. object is not a map;
          2. +
          3. any of tags is not a keyword or sequence of keywords.
          4. +
          +

          It’s legal to include sequences of keywords in tags, so that users can do useful things like (tag obj (map keyword some-strings)).

          tagged?

          (tagged? object & tags)

          True if this object is tagged with each of these tags. It is an error (and an exception will be thrown) if

          1. object is not a map;
          2. any of tags is not a keyword.
          3. -

          tagged?

          (tagged? object & tags)

          True if this object is tagged with each of these tags. It is an error (and an exception will be thrown) if

          +

          tags

          macro

          (tags object)

          Return the tags of this object, if any.

          untag

          (untag object & tags)

          Return an object like this object but with these tags removed from its tags, if present. It is an error (and an exception will be thrown) if

          1. object is not a map;
          2. -
          3. any of tags is not a keyword.
          4. -

          tags

          macro

          (tags object)

          Return the tags of this object, if any.

          untag

          (untag object & tags)

          Return an object like this object but with these tags removed from its tags, if present. It is an error (and an exception will be thrown) if

          -
            -
          1. object is not a map;
          2. -
          3. any of tags is not a keyword.
          4. -
          \ No newline at end of file +
        • any of tags is not a keyword or sequence of keywords.
        • +
          \ No newline at end of file diff --git a/docs/codox/walkmap.utils.html b/docs/codox/walkmap.utils.html index eed6c5e..51a9a3a 100644 --- a/docs/codox/walkmap.utils.html +++ b/docs/codox/walkmap.utils.html @@ -1,3 +1,5 @@ -walkmap.utils documentation

          walkmap.utils

          Miscellaneous utility functions.

          deep-merge

          (deep-merge & vals)

          Recursively merges maps. If vals are not maps, the last value wins.

          vertices

          (vertices o)

          If o is an object with vertices, return those vertices, else nil.

          \ No newline at end of file +walkmap.utils documentation

          walkmap.utils

          Miscellaneous utility functions.

          =ish

          (=ish n1 n2)(=ish n1 n2 tolerance)

          True if numbers n1, n2 are roughly equal; that is to say, equal to within tolerance (defaults to one part in a million).

          check-kind-type

          macro

          (check-kind-type object expected)(check-kind-type object checkfn expected)

          If object is not of kind-type expected, throws an IllegalArgumentException with an appropriate message; otherwise, returns object. If checkfn is supplied, it should be a function which tests whether the object is of the expected kind-type.

          +

          Macro, so that the exception is thrown from the calling function.

          check-kind-type-seq

          macro

          (check-kind-type-seq s expected)(check-kind-type-seq s checkfn expected)

          If some item on sequence s is not of the expected kind-type, throws an IllegalArgumentException with an appropriate message; otherwise, returns object. If checkfn is supplied, it should be a function which tests whether the object is of the expected kind-type.

          +

          Macro, so that the exception is thrown from the calling function.

          deep-merge

          (deep-merge & vals)

          Recursively merges maps. If vals are not maps, the last value wins.

          kind-type

          (kind-type object)

          Identify the type of an object, e.g. for logging. If it has a :kind key, it’s one of ours, and that’s what we want. Otherwise, we want its type; but the type of nil is nil, which doesn’t get printed when assembling error ,essages, so return “nil”.

          truncate

          (truncate s n)

          If string s is more than n characters long, return the first n characters; otherwise, return s.

          \ No newline at end of file diff --git a/docs/codox/walkmap.vertex.html b/docs/codox/walkmap.vertex.html index 129bced..95d3948 100644 --- a/docs/codox/walkmap.vertex.html +++ b/docs/codox/walkmap.vertex.html @@ -1,7 +1,7 @@ -walkmap.vertex documentation

          walkmap.vertex

          Essentially the specification for things we shall consider to be vertices.

          -

          Note that there’s no distance function here; to find the distance between two vertices, create an edge from them and use walkmap.edge/length.

          canonicalise

          (canonicalise o)

          If o is a map with numeric values for :x, :y and optionally :z, upgrade it to something we will recognise as a vertex.

          ensure2d

          If o is a vertex, set its :z value to zero; else throw an exception.

          ensure3d

          Given a vertex o, if o has a :z value, just return o; otherwise return a vertex like o but having thie dflt value as the value of its :z key, or zero as the value of its :z key if dflt is not specified.

          -

          If o is not a vertex, throws an exception.

          vertex

          (vertex x y)(vertex x y z)

          Make a vertex with this x, y and (if provided) z values. Returns a map with those values, plus a unique :id value, and :kind set to :vertex. It’s not necessary to use this function to create a vertex, but the :id must be present and must be unique.

          vertex-key

          (vertex-key o)

          Making sure we get the same key everytime we key a vertex with the same coordinates. o must have numeric values for :x, :y, and optionally :z; it is an error and an exception will be thrown if o does not conform to this specification.

          -

          Note: these keys can be quite long. No apology is made: it is required that the same key can never refer to two different locations in space.

          vertex?

          (vertex? o)

          True if o satisfies the conditions for a vertex. That is, essentially, that it must rerpresent a two- or three- dimensional vector. A vertex is shall be a map having at least the keys :x and :y, where the value of those keys is a number. If the key :z is also present, its value must also be a number.

          -

          The name vector? was not used as that would clash with a function of that name in clojure.core whose semantics are entirely different.

          \ No newline at end of file +walkmap.vertex documentation

          walkmap.vertex

          Essentially the specification for things we shall consider to be vertices.

          +

          Note that there’s no distance function here; to find the distance between two vertices, create an edge from them and use walkmap.edge/length.

          canonicalise

          (canonicalise o)

          If o is a map with numeric values for :x, :y and optionally :z, upgrade it to something we will recognise as a vertex.

          check-vertex

          macro

          (check-vertex o)

          If o is not a vertex, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          check-vertices

          macro

          (check-vertices o)

          If o is not a sequence of vertices, throw an IllegalArgumentException with an appropriate message; otherwise, returns o. Macro, so exception is thrown from the calling function.

          ensure2d

          If o is a vertex, set its :z value to zero; else throw an exception.

          ensure3d

          Given a vertex o, if o has a :z value, just return o; otherwise return a vertex like o but having this dflt value as the value of its :z key, or zero as the value of its :z key if dflt is not specified.

          +

          If o is not a vertex, throws an exception.

          vertex

          (vertex x y)(vertex x y z)

          Make a vertex with this x, y and (if provided) z values. Returns a map with those values, plus a unique :walkmap.id/id value, and :kind set to :vertex. It’s not necessary to use this function to create a vertex, but the :walkmap.id/id must be present and must be unique.

          vertex*

          (vertex* v1 v2)

          Return a vertex like v1, but with each of its coordinates multiplied by the equivalent vertex in v2. It is an error, and an exception will be thrown, if either v1 or v2 is not a vertex.

          vertex-key

          (vertex-key o)

          Making sure we get the same key everytime we key a vertex with the same coordinates. o must have numeric values for :x, :y, and optionally :z; it is an error and an exception will be thrown if o does not conform to this specification.

          +

          Note: these keys can be quite long. No apology is made: it is required that the same key can never refer to two different locations in space.

          vertex=

          (vertex= v1 v2)

          True if vertices v1, v2 represent the same vertex.

          vertex?

          (vertex? o)

          True if o satisfies the conditions for a vertex. That is, essentially, that it must rerpresent a two- or three- dimensional vector. A vertex is shall be a map having at least the keys :x and :y, where the value of those keys is a number. If the key :z is also present, its value must also be a number.

          +

          The name vector? was not used as that would clash with a function of that name in clojure.core whose semantics are entirely different.

          within-box?

          (within-box? target minv maxv)

          True if target is within the box defined by minv and maxv. All arguments must be vertices; additionally, both minv and maxv must have :z coordinates.

          \ No newline at end of file diff --git a/project.clj b/project.clj index 2636161..b0f66d6 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,8 @@ :doc/format :markdown} :output-path "docs/codox" :source-uri "https://github.com/simon-brooke/walkmap/blob/master/{filepath}#L{line}"} - :dependencies [[org.clojure/clojure "1.8.0"] + :dependencies [[org.clojure/algo.generic "0.1.3"] + [org.clojure/clojure "1.8.0"] [org.clojure/data.zip "1.0.0"] [org.clojure/math.numeric-tower "0.0.4"] [org.clojure/math.combinatorics "0.1.6"] diff --git a/src/walkmap/edge.clj b/src/walkmap/edge.clj index 52ad4c9..d6a042f 100644 --- a/src/walkmap/edge.clj +++ b/src/walkmap/edge.clj @@ -3,7 +3,8 @@ An edge is a line segment having just a start and an end, with no intervening nodes." (:require [clojure.math.numeric-tower :as m] - [walkmap.vertex :refer [ensure2d ensure3d vertex vertex= vertex?]])) + [walkmap.utils :as u] + [walkmap.vertex :refer [canonicalise ensure2d ensure3d vertex vertex= vertex?]])) (defn edge "Return an edge between vertices `v1` and `v2`." @@ -51,13 +52,14 @@ [e] (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} l (length e')] - (reduce - merge - {} - (map - (fn [k] - {k (/ (- (k (:end e')) (k (:start e'))) l)}) - [:x :y :z])))) + (canonicalise + (reduce + merge + {} + (map + (fn [k] + {k (/ (- (k (:end e')) (k (:start e'))) l)}) + [:x :y :z]))))) (defn parallel? "True if all `edges` passed are parallel with one another." diff --git a/src/walkmap/ocean.clj b/src/walkmap/ocean.clj index 44abd35..99004ca 100644 --- a/src/walkmap/ocean.clj +++ b/src/walkmap/ocean.clj @@ -1,5 +1,6 @@ (ns walkmap.ocean - "Deal with (specifically, at this stage, cull) ocean areas") + "Deal with (specifically, at this stage, cull) ocean areas" + (:require [walkmap.utils :refer [=ish]])) (def ^:dynamic *sea-level* "The sea level on heightmaps we're currently handling. If characters are to @@ -14,7 +15,7 @@ "Of a `facet`, is the altitude of every vertice equal to `*sea-level*`?" [facet] (every? - #(= % *sea-level*) + #(=ish % *sea-level*) (map :z (:vertices facet)))) (defn cull-ocean-facets diff --git a/src/walkmap/path.clj b/src/walkmap/path.clj index 92222a4..4b0d9a3 100644 --- a/src/walkmap/path.clj +++ b/src/walkmap/path.clj @@ -4,8 +4,9 @@ feature, where such features specifically include watercourses." (:require [clojure.string :as s] [walkmap.edge :as e] - [walkmap.polygon :refer [polygon?]] - [walkmap.utils :refer [kind-type]] + [walkmap.polygon :refer [check-polygon polygon?]] + [walkmap.tag :refer [tag tags]] + [walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]] [walkmap.vertex :refer [vertex?]])) (defn path? @@ -17,7 +18,7 @@ [v (:vertices o)] (and (seq? v) - (> (count v) 2) + (> (count v) 1) (every? vertex? v) (:walkmap.id/id o) (or (nil? (:kind o)) (= (:kind o) :path))))) @@ -25,12 +26,25 @@ (defn path "Return a path constructed from these `vertices`." [& vertices] - (when-not (every? vertex? vertices) - (throw (IllegalArgumentException. - (str - "Each item on path must be a vertex: " - (s/join " " (map kind-type (remove vertex? vertices))))))) - {:vertices vertices :walkmap.id/id (keyword (gensym "path")) :kind :path}) + (check-kind-type-seq vertices vertex? :vertex) + (if + (> (count vertices) 1) + {:vertices vertices :walkmap.id/id (keyword (gensym "path")) :kind :path} + (throw (IllegalArgumentException. "Path must have more than one vertex.")))) + +(defmacro check-path + "If `o` is not a path, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type ~o path? :path)) + +(defmacro check-paths + "If `o` is not a sequence of paths, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type-seq ~o path? :path)) (defn polygon->path "If `o` is a polygon, return an equivalent path. What's different about @@ -40,8 +54,8 @@ If `o` is not a polygon, will throw an exception." [o] - (when-not (polygon? o) - (throw (IllegalArgumentException. (str "Not a polygon: " (kind-type o))))) +;; this is breaking, but I have NO IDEA why! +;; (check-polygon o polygon? :polygon) (assoc (dissoc o :vertices) :kind :path ;; `concat` rather than `conj` because order matters. @@ -55,18 +69,17 @@ sequence of vertices." [o] (cond - (seq? o) - (when - (and - (vertex? (first o)) - (vertex? (first (rest o)))) - (cons - ;; TODO: think about: when constructing an edge from a path, should the - ;; constructed edge be tagged with the tags of the path? - (e/edge (first o) (rest o)) - (path->edges (rest o)))) - (path? o) - (path->edges (:vertices o)) + (seq? o) (when + (and + (vertex? (first o)) + (vertex? (first (rest o)))) + (cons + ;; TODO: think about: when constructing an edge from a path, should the + ;; constructed edge be tagged with the tags of the path? + (e/edge (first o) (first (rest o))) + (path->edges (rest o)))) + (path? o) (path->edges (:vertices o)) + (polygon? o) (path->edges (polygon->path o)) :else (throw (IllegalArgumentException. "Not a path or sequence of vertices!")))) @@ -78,7 +91,4 @@ 2. It is not even quite the same as the length of the path *as rendered*, since paths will generally be rendered as spline curves." [path] - (if - (path? path) - (reduce + (map e/length (path->edges path))) - (throw (IllegalArgumentException. "Not a path!")))) + (reduce + (map e/length (path->edges (check-path path))))) diff --git a/src/walkmap/polygon.clj b/src/walkmap/polygon.clj index ab6420e..5b0ce79 100644 --- a/src/walkmap/polygon.clj +++ b/src/walkmap/polygon.clj @@ -3,8 +3,8 @@ (:require [clojure.string :as s] [walkmap.edge :as e] [walkmap.tag :as t] - [walkmap.utils :refer [kind-type]] - [walkmap.vertex :refer [vertex vertex?]])) + [walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]] + [walkmap.vertex :refer [check-vertices vertex vertex?]])) (defn polygon? "True if `o` satisfies the conditions for a polygon. A polygon shall be a @@ -20,6 +20,20 @@ (:walkmap.id/id o) (or (nil? (:kind o)) (= (:kind o) :polygon))))) +(defmacro check-polygon + "If `o` is not a polygon, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type ~o polygon? :polygon)) + +(defmacro check-polygons + "If `o` is not a sequence of polygons, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type-seq ~o polygon? :polygon)) + (defn triangle? "True if `o` satisfies the conditions for a triangle. A triangle shall be a polygon with exactly three vertices." @@ -28,24 +42,26 @@ (coll? o) (= (count (:vertices o)) 3))) +(defmacro check-triangle + "If `o` is not a triangle, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type ~o triangle? :triangle)) + (defn polygon "Return a polygon constructed from these `vertices`." - [vertices] - (when-not (every? vertex? vertices) - (throw (IllegalArgumentException. - (str - "Each item on vertices must be a vertex: " - (s/join " " (map kind-type (remove vertex? vertices))))))) - {:vertices vertices :walkmap.id/id (keyword (gensym "poly")) :kind :polygon}) + [& vertices] + {:vertices (check-vertices vertices) + :walkmap.id/id (keyword (gensym "poly")) + :kind :polygon}) (defn gradient "Return a polygon like `triangle` but with a key `:gradient` whose value is a unit vector representing the gradient across `triangle`." [triangle] - (when-not (triangle? triangle) - (throw (IllegalArgumentException. - (s/join " " ["Must be a triangle:" (kind-type triangle)])))) - (let [order (sort #(max (:z %1) (:z %2)) (:vertices triangle)) + (let [order (sort #(max (:z %1) (:z %2)) + (:vertices (check-triangle triangle))) highest (first order) lowest (last order)] (assoc triangle :gradient (e/unit-vector (e/edge lowest highest))))) @@ -57,10 +73,7 @@ `walkmap.polygon`. It is an error (although no exception is currently thrown) if the object past is not a triangular polygon." [facet] - (when-not (triangle? facet) - (throw (IllegalArgumentException. - (s/join " " ["Must be a triangle:" (kind-type facet)])))) - (let [vs (:vertices facet) + (let [vs (:vertices (check-triangle facet)) v1 (first vs) opposite (e/edge (nth vs 1) (nth vs 2)) oc (e/centre opposite)] @@ -74,10 +87,7 @@ (defn centre [poly] - (when-not (polygon? poly) - (throw (IllegalArgumentException. - (s/join " " ["Must be a polygon:" (kind-type poly)])))) - (case (count (:vertices poly)) + (case (count (:vertices (check-polygon poly))) 3 (triangle-centre poly) ;; else (throw diff --git a/src/walkmap/utils.clj b/src/walkmap/utils.clj index 109d981..2343eff 100644 --- a/src/walkmap/utils.clj +++ b/src/walkmap/utils.clj @@ -1,6 +1,7 @@ (ns walkmap.utils "Miscellaneous utility functions." - (:require [clojure.math.numeric-tower :as m])) + (:require [clojure.math.numeric-tower :as m] + [clojure.string :as s])) (defn deep-merge "Recursively merges maps. If vals are not maps, the last value wins." @@ -41,3 +42,60 @@ (if (and (number? n1) (number? n2)) (< (m/abs (- n1 n2)) tolerance) (= n1 n2)))) + +(defmacro check-kind-type + "If `object` is not of kind-type `expected`, throws an + IllegalArgumentException with an appropriate message; otherwise, returns + `object`. If `checkfn` is supplied, it should be a function which tests + whether the object is of the expected kind-type. + + Macro, so that the exception is thrown from the calling function." + ([object expected] + `(if-not (= (kind-type ~object) ~expected) + (throw + (IllegalArgumentException. + (s/join + " " + ["Expected" ~expected "but found" (kind-type ~object)]))) + ~object)) + ([object checkfn expected] + `(if-not (~checkfn ~object) + (throw + (IllegalArgumentException. + (s/join + " " + ["Expected" ~expected "but found" (kind-type ~object)]))) + ~object))) + +(defmacro check-kind-type-seq + "If some item on sequence `s` is not of the `expected` kind-type, throws an + IllegalArgumentException with an appropriate message; otherwise, returns + `object`. If `checkfn` is supplied, it should be a function which tests + whether the object is of the expected kind-type. + + Macro, so that the exception is thrown from the calling function." + ([s expected] + `(if-not (every? #(= (kind-type %) ~expected) ~s) + (throw + (IllegalArgumentException. + (s/join + " " + ["Expected sequence of" + ~expected + "but found (" + (s/join ", " (remove #(= ~expected %) (map kind-type ~s))) + ")"]))) + ~s)) + ([s checkfn expected] + `(if-not (every? #(~checkfn %) ~s) + (throw + (IllegalArgumentException. + (s/join + " " + ["Expected sequence of" + ~expected + "but found (" + (s/join ", " (remove #(= ~expected %) (map kind-type ~s))) + ")"]))) + ~s))) + diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index ca894c4..4fb8555 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -6,7 +6,7 @@ (:require [clojure.math.numeric-tower :as m] [clojure.string :as s] [taoensso.timbre :as l] - [walkmap.utils :refer [=ish kind-type truncate]])) + [walkmap.utils :refer [=ish check-kind-type check-kind-type-seq kind-type truncate]])) (defn vertex-key "Making sure we get the same key everytime we key a vertex with the same @@ -48,31 +48,43 @@ (or (nil? (:z o)) (number? (:z o))) (or (nil? (:kind o)) (= (:kind o) :vertex)))) +(defmacro check-vertex + "If `o` is not a vertex, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type ~o vertex? :vertex)) + +(defmacro check-vertices + "If `o` is not a sequence of vertices, throw an `IllegalArgumentException` with an + appropriate message; otherwise, returns `o`. Macro, so exception is thrown + from the calling function." + [o] + `(check-kind-type-seq ~o vertex? :vertex)) + (defn vertex= "True if vertices `v1`, `v2` represent the same vertex." [v1 v2] + (check-vertex v1) + (check-vertex v2) (every? #(=ish (% v1) (% v2)) [:x :y :z])) (defn vertex* "Return a vertex like `v1`, but with each of its coordinates multiplied - by the equivalent vertex in `v2`." + by the equivalent vertex in `v2`. It is an error, and an exception will + be thrown, if either `v1` or `v2` is not a vertex." [v1 v2] - (if - (and (vertex? v1) (vertex? v2)) - (let [f (fn [v1 v2 coord] - (* (or (coord v1) 0) - ;; one here is deliberate! - (or (coord v2) 1)))] - (assoc v1 :x (f v1 v2 :x) - :y (f v1 v2 :y) - :z (f v1 v2 :z))) - (do (l/warn - (s/join - " " - ["in `vertex-multiply`, both must be vectors. v1:" v1 "v2:" v2])) - v1))) + (check-vertex v1) + (check-vertex v2) + (let [f (fn [v1 v2 coord] + (* (or (coord v1) 0) + ;; one here is deliberate! + (or (coord v2) 1)))] + (assoc v1 :x (f v1 v2 :x) + :y (f v1 v2 :y) + :z (f v1 v2 :z)))) (defn vertex "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map @@ -83,7 +95,7 @@ (let [v {:x x :y y :kind :vertex}] (assoc v :walkmap.id/id (vertex-key v)))) ([x y z] - (let [v (assoc (vertex x y) :z z)] + (let [v {:x x :y y :z z :kind :vertex}] (assoc v :walkmap.id/id (vertex-key v))))) (defn canonicalise @@ -106,7 +118,7 @@ (def ensure3d "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise - return a vertex like `o` but having thie `dflt` value as the value of its + return a vertex like `o` but having this `dflt` value as the value of its `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. If `o` is not a vertex, throws an exception." @@ -115,33 +127,22 @@ ([o] (ensure3d o 0.0)) ([o dflt] - (cond - (not (vertex? o)) (throw - (IllegalArgumentException. - (truncate (str "Not a vertex: " (or o "nil")) 80))) - (:z o) o - :else (assoc o :z dflt)))))) + (if (:z (check-vertex o)) + o + (assoc o :z dflt)))))) (def ensure2d "If `o` is a vertex, set its `:z` value to zero; else throw an exception." (memoize (fn [o] - (if - (vertex? o) - (assoc o :z 0.0) - (throw - (IllegalArgumentException. - (truncate (str "Not a vertex: " (or o "nil")) 80))))))) + (assoc (check-vertex o) :z 0.0)))) (defn within-box? "True if `target` is within the box defined by `minv` and `maxv`. All arguments must be vertices; additionally, both `minv` and `maxv` must have `:z` coordinates." [target minv maxv] - (when-not (and (vertex? target) (vertex? minv) (vertex? maxv)) - (throw (IllegalArgumentException. - (s/join " " ["Arguments to `within-box?` must be vertices:" - (map kind-type [target minv maxv])])))) + (check-vertices [target minv maxv]) (every? (map #(< (% minv) (or (% target) 0) (% maxv)) diff --git a/test/walkmap/edge_test.clj b/test/walkmap/edge_test.clj index 95a0cda..697f43e 100644 --- a/test/walkmap/edge_test.clj +++ b/test/walkmap/edge_test.clj @@ -2,7 +2,7 @@ (:require [clojure.math.numeric-tower :as m] [clojure.test :refer :all] [walkmap.edge :refer :all] - [walkmap.vertex :refer [vertex]])) + [walkmap.vertex :refer [vertex vertex=]])) (deftest edge-test (testing "identification of edges." @@ -98,12 +98,12 @@ (deftest parallel-test (testing "parallelism" - (is (parallel? {:start {:x 0.0 :y 0.0 :z 0.0 :walkmap.id/id 'foo} :end {:x 3 :y 4 :z 0.0 :walkmap.id/id 'bar}} - {:start {:x 1.0 :y 2.0 :z 3.5 :walkmap.id/id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :walkmap.id/id 'bar}}) + (is (parallel? (edge (vertex 0.0 0.0 0.0) (vertex 3 4 0.0)) + (edge (vertex 1.0 2.0 3.5) (vertex 4.0 6.0 3.5))) "Should be") (is (not - (parallel? {:start {:x 0.0 :y 0.0 :z 0.0 :walkmap.id/id 'foo} :end {:x 3 :y 4 :z 0.0 :walkmap.id/id 'bar}} - {:start {:x 1.0 :y 2.0 :z 3.5 :walkmap.id/id 'foo} :end {:x 4.0 :y 6.0 :z 3.49 :walkmap.id/id 'bar}})) + (parallel? (edge (vertex 0.0 0.0 0.0) (vertex 3 4 0.0)) + (edge (vertex 1.0 2.0 3.5) (vertex 4.0 6.0 3.49)))) "Should not be!"))) (deftest overlaps2d-test @@ -113,9 +113,9 @@ (deftest unit-vector-test (testing "deriving the unit vector" - (is (= - (unit-vector {:start {:x 0.0 :y 0.0 :z 0.0 :walkmap.id/id 'foo} :end {:x 3 :y 4 :z 0.0 :walkmap.id/id 'bar}}) - {:x 0.6, :y 0.8, :z 0.0})) - (is (= - (unit-vector {:start {:x 1.0 :y 2.0 :z 3.5 :walkmap.id/id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :walkmap.id/id 'bar}}) - {:x 0.6, :y 0.8, :z 0.0})))) + (is (vertex= + (unit-vector (edge (vertex 0.0 0.0 0.0) (vertex 3 4 0.0))) + (vertex 0.6 0.8 0.0))) + (is (vertex= + (unit-vector (edge (vertex 1.0 2.0 3.5) (vertex 4.0 6.0 3.5))) + (vertex 0.6 0.8 0.0))))) diff --git a/test/walkmap/ocean_test.clj b/test/walkmap/ocean_test.clj new file mode 100644 index 0000000..843aa4d --- /dev/null +++ b/test/walkmap/ocean_test.clj @@ -0,0 +1,53 @@ +(ns walkmap.ocean-test + (:require [clojure.test :refer :all] + [walkmap.ocean :refer :all] + [walkmap.polygon :refer [polygon]] + [walkmap.vertex :refer [vertex vertex=]])) + +(deftest ocean-tests + (testing "Identification of polygons at sea level" + (is (ocean? (polygon (vertex 0 0 0) (vertex 0 1 0) (vertex 1 0 0))) + "All `:z` coordinates are zero, and default binding for `*sea-level*` + => ocean.") + (is (false? (ocean? (polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0)))) + "Not all `:z` coordinates are zero, and default binding for `*sea-level*` + => not ocean.") + (is (false? (ocean? (polygon (vertex 0 0 5) (vertex 0 1 5) (vertex 1 0 5)))) + "Not all `:z` coordinates are five, and default binding for `*sea-level*` + => not ocean.") + (binding [*sea-level* 5] + (is (false? (ocean? (polygon (vertex 0 0 0) (vertex 0 1 0) (vertex 1 0 0)))) + "All `:z` coordinates are zero, and `*sea-level*` rebound to five + => not ocean.") + (is (false? (ocean? (polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0)))) + "Not all `:z` coordinates are zero, and `*sea-level*` rebound to five + => not ocean.") + (is (ocean? (polygon (vertex 0 0 5) (vertex 0 1 5) (vertex 1 0 5))) + "Not all `:z` coordinates are five, and `*sea-level*` rebound to five + => ocean.")))) + +(deftest cull-ocean-facets-tests + (testing "Culling of ocean facets (not currently used)." + (let [stl {:facets [(polygon (vertex 0 0 0) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 5) (vertex 0 1 5) (vertex 1 0 5))]} + expected {:facets + [(polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 5) (vertex 0 1 5) (vertex 1 0 5))]} + actual (cull-ocean-facets stl)] + (map + #(is (vertex= (nth (:facets expected) %) (nth (:facets actual) %)) + (str "Facet " % " did not match.")) + (range (max (count (:facets expected)) (count (:facets actual)))))) + (binding [*sea-level* 5] + (let [stl {:facets [(polygon (vertex 0 0 0) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 5) (vertex 0 1 5) (vertex 1 0 5))]} + expected {:facets + [(polygon (vertex 0 0 0) (vertex 0 1 0) (vertex 1 0 0)) + (polygon (vertex 0 0 1) (vertex 0 1 0) (vertex 1 0 0))]} + actual (cull-ocean-facets stl)] + (map + #(is (vertex= (nth (:facets expected) %) (nth (:facets actual) %)) + (str "Facet " % " did not match.")) + (range (max (count (:facets expected)) (count (:facets actual))))))))) diff --git a/test/walkmap/path_test.clj b/test/walkmap/path_test.clj new file mode 100644 index 0000000..5d66c6c --- /dev/null +++ b/test/walkmap/path_test.clj @@ -0,0 +1,109 @@ +(ns walkmap.path-test + (:require [clojure.test :refer :all] + [walkmap.edge :refer [edge?]] + [walkmap.path :refer :all] + [walkmap.polygon :refer [polygon]] + [walkmap.utils :refer [kind-type]] + [walkmap.vertex :refer [vertex vertex=]])) + +(deftest path-tests + (testing "Path instantiation" + (is (= (kind-type (path (vertex 0 0 0) (vertex 1 1 1))) :path) + "Paths should be identified as paths.") + (is (path? (path (vertex 0 0 0) (vertex 1 1 1))) + "Paths should test as paths.") + (is (check-path (path (vertex 0 0 0) (vertex 1 1 1))) + "No exception should be thrown when checking a valid path.") + (is (thrown? + IllegalArgumentException + (check-path + (update-in + (path (vertex 0 0 0) (vertex 1 1 1)) + :vertices + conj + "Not a vertex"))) + "Checking an invalid path should throw an exception.") + (is (thrown? + IllegalArgumentException + (path (vertex 0 0 0))) + "Too short.") + (is (thrown? + IllegalArgumentException + (path (vertex 0 0 0) (vertex 1 1 1) "Not a vertex")) + "Non-vertex included.") + (is (thrown? + IllegalArgumentException + (path (vertex 0 0 0) (vertex 1 1 1) "Not a vertex.")) + "Passing something which is not a vertex when constructing a path whould + cause an exception to be thrown."))) + +(deftest conversion-tests + (testing "Converting polygons to paths" + (let [poly (polygon (vertex 0 0 0) (vertex 1 0 0) (vertex 1 1 0) (vertex 0 1 0)) + p (polygon->path poly)] + (is (path? p) "Should be a path.") + (is (vertex= (first p) (last p)) + "First and last vertices of the generated path should be equal to + one another.") + (is (= (count (:vertices path)) (inc (count (:vertices poly)))) + "The generated path should have one more vertex than the polygon.") + (map + #(is (vertex= (nth (:vertices poly) %) (nth (:vertices p) %)) + (str "Vertex " % " from each set of vertices should be the same.")) + (range (count (:vertices poly)))))) + (testing "Converting polygons and paths to edges." + (let [poly (polygon (vertex 0 0 0) (vertex 1 0 0) (vertex 1 1 0) (vertex 0 1 0)) + edges (path->edges poly)] + (is (every? edge? edges) + "Every returned edge should be an edge.") + (is (= (count (:vertices poly)) (count edges)) + "There should be the same number of edges as the vertices of the polygon") + (map + #(is + (vertex= (nth (:vertices poly) %) (:start (nth edges %))) + (str + "Each edge should start from the same place as the corresponding + vertex: " %)) + (range (count (:vertices poly)))) + (map + #(is + (vertex= (nth (:vertices poly) (mod (inc %) (count (:vertices poly)))) + (:end (nth edges %))) + (str + "Each edge should end at the same place as the subsequent + vertex: " %)) + (range (count (:vertices poly))))) + (is (thrown? IllegalArgumentException + (path->edges "Not a legal argument."))))) + +(deftest check-paths-tests + (testing "Checking multiple paths." + (is (thrown? IllegalArgumentException + (check-paths [(path (vertex 0 0 0) + (vertex 1 0 0) + (vertex 1 1 0) + (vertex 0 1 0) + (vertex 0 0 0)) + (path (vertex 0 0 1) + (vertex 1 0 1) + (vertex 1 1 1) + (vertex 0 1 1) + (vertex 0 0 1)) + (vertex 0 0 0)])) + "Not all elements are paths") + (is (check-paths [(path (vertex 0 0 0) + (vertex 1 0 0) + (vertex 1 1 0) + (vertex 0 1 0) + (vertex 0 0 0)) + (path (vertex 0 0 1) + (vertex 1 0 1) + (vertex 1 1 1) + (vertex 0 1 1) + (vertex 0 0 1))]) + "All elements are paths"))) + +(deftest length-tests + (testing "length of paths" + (let [p (path (vertex 0 0 0) (vertex 1 0 0) (vertex 1 1 0) (vertex 0 1 0) (vertex 0 0 0))] + (is (= (length p) 4) "By inspection.")))) diff --git a/test/walkmap/polygon_test.clj b/test/walkmap/polygon_test.clj new file mode 100644 index 0000000..7f378b8 --- /dev/null +++ b/test/walkmap/polygon_test.clj @@ -0,0 +1,81 @@ +(ns walkmap.polygon-test + (:require [clojure.test :refer :all] +;; [clojure.algo.generic.math-functions :as m] +;; [walkmap.edge :refer [edge?]] +;; [walkmap.path :refer :all] + [walkmap.polygon :refer :all] + [walkmap.utils :refer [kind-type]] + [walkmap.vertex :refer [vertex vertex? vertex=]]) + ) + +(deftest polygon-tests + (testing "Constructing polygons" + (let [square (polygon (vertex 0 0 0) (vertex 1 0 0) + (vertex 1 1 0) (vertex 0 1 0)) + triangle (polygon (vertex 0 0 0) (vertex 0 3 0) + (vertex 4 0 0))] + (is (= (kind-type square) :polygon) + "Square should have `:kind` = `:polygon`.") + (is (= (kind-type triangle) :polygon) + "Triangle should have `:kind` = `:polygon`.") + (is (polygon? square) "Square should be a polygon.") + (is (polygon? triangle) "Triangle should be a polygon.") + (is (false? (triangle? square)) "Square is not a triangle.") + (is (triangle? triangle) "Triangle is a triangle.") + (is (check-polygon square) "No exception should be thrown.") + (is (check-polygon triangle) "No exception should be thrown.") + (is (check-triangle triangle) "No exception should be thrown.") + (is (check-polygons [square triangle]) + "No exception should be thrown.") + (is (thrown? + IllegalArgumentException + (check-polygon "Not a polygon")) "Not a polygon") + (is (thrown? + IllegalArgumentException + (check-polygons [square triangle "Not a polygon"])) + "One value is not a polygon.") + (is (thrown? + IllegalArgumentException (check-triangle square)) + "Not a triangle.") + (is (thrown? + IllegalArgumentException (polygon (vertex 0 0 0) (vertex 1 0 0))) + "Too few vertices.") + (is (thrown? + IllegalArgumentException (polygon (vertex 0 0 0) (vertex 1 0 0) + (vertex 1 1 0) "Not a vertex" + (vertex 0 1 0))) + "Non-vertex included.") + ) + )) + +(deftest gradient-tests + (testing "Finding the gradient across a triangle." + (let [tri (polygon (vertex 0 0 1) (vertex 1 0 0) (vertex 1 1 0.5)) + gra (gradient tri)] + (is (nil? (:gradient tri)) "Basic trangle should not have a gradient.") + (is (vertex? (:gradient gra)) + "After passing through gradient function, it should have a gradient.") + ;; TODO: I need to check that the gradient is being computed correclt, + ;; but my brain isn't up to the trigonometry just now. + ))) + +(deftest centre-tests + (testing "Finding the centres of polygons." + (let [square (polygon (vertex 0 0 0) (vertex 1 0 0) + (vertex 1 1 0) (vertex 0 1 0)) + triangle (polygon (vertex 0 0 0) (vertex 0 3 0) + (vertex 4 0 0)) + centred (centre triangle)] + (is (vertex= (:centre centred) (vertex 1.3333333 1.0 0.0)) + "By inspection (check this maths!).") + (is (thrown? + UnsupportedOperationException + (centre square)) + "We can't yet find the centre of a quadrilateral, but we should be + able to do so, so it isn't an illegal argument, it just doesn't + work.") + (is (thrown? + IllegalArgumentException + (centre "Not a polygon")) + "Anything else that isn't a polygon, though, is an illegal argument.")))) + diff --git a/test/walkmap/vertex_test.clj b/test/walkmap/vertex_test.clj new file mode 100644 index 0000000..dbdbb30 --- /dev/null +++ b/test/walkmap/vertex_test.clj @@ -0,0 +1,45 @@ +(ns walkmap.utils-test + (:require [clojure.test :refer :all] + [walkmap.vertex :refer :all])) + +(deftest vertex-equal-tests + (testing "Equality of vertices" + (is (vertex= (vertex 0 0 0) (vertex 0 0 0)) + "should be equal") + (is (vertex= (vertex 0 0 0) (vertex 0.0000001 0 0)) + "differences less than one part in a million should be ignored") + (is (vertex= (vertex 0 0 0) (vertex 0 0 1)) + "should not be equal") + (is (thrown? IllegalArgumentException + (vertex= (vertex 0 0 0) "Not a vertex")) + "Exception should be thrown: not a vertex."))) + +(deftest vertex-multiply-tests + (testing "multiplication of vertices" + (let [v (vertex (rand) (rand) (rand)) + u (vertex 1 1 1) + v' (vertex* v u)] + (is (vertex= v v') + "Multiplication by {:x 1 :y 1 :z 1} should not change the vertex")) + (let [v (vertex 0.333333 0.25 0.2) + d (vertex 3 4 5) + v' (vertex* v d) + expected (vertex 1 1 1)] + (is (vertex= expected v') + "Multiplication by values other than {:x 1 :y 1 :z 1} should change + the vertex")) + (let [v (vertex 0.333333 0.25 0.2) + d (vertex 3 4) + v' (vertex* v d) + expected (vertex 1 1 0.2)] + (is (vertex= expected v') + "Multiplication by a 2D vertex should not change `:z`")) + (let [v (vertex 0.333333 0.25) + d (vertex 3 4) + v' (vertex* v d) + expected (vertex 1 1 0)] + (is (vertex= expected v') + "Multiplication of a 2D vertex should result in `:z` = zero")) + (is (thrown? IllegalArgumentException + (vertex* (vertex 0 0 0) "Not a vertex")) + "Exception should be thrown: not a vertex.")))