diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index e26e161..f5c20a4 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -27,29 +27,29 @@ walkmap.edge
164
-100.00 % + style="width:98.31697054698458%; + float:left;"> 701
12
+98.32 %
40
+ style="width:95.1923076923077%; + float:left;"> 99
5
100.00 % -70740 +18918104 walkmap.geometry
2
121
-1.63 % + style="width:100.0%; + float:left;"> 62 +100.00 %
2
11
-15.38 % -24313 + style="width:100.0%; + float:left;"> 10 +100.00 % +17110 walkmap.ocean
32
15.79 % -76738 +78738 walkmap.polygon
42
4
-91.30 % + style="width:90.56603773584905%; + float:left;"> 48
5
+90.57 %
8
9
1
100.00 % -1839 +19310 walkmap.stl
193
178
-52.02 % + style="width:56.777996070726914%; + float:left;"> 289
220
+56.78 %
28
9
39
-48.68 % -1481376 + style="width:44.0%; + float:left;"> 44
10
46
+54.00 % +19114100 walkmap.superstructure
23
14.81 % -74827 +85927 walkmap.svg
7
30.00 % -26210 +23310 walkmap.vertex
214
38
-84.92 % + style="width:78.08641975308642%; + float:left;"> 253
71
+78.09 %
28
7
7
-83.33 % -82942 + style="width:70.0%; + float:left;"> 42
6
12
+80.00 % +1141160 Totals: -44.01 % +61.24 % -50.55 % +61.28 % diff --git a/docs/cloverage/walkmap/edge.clj.html b/docs/cloverage/walkmap/edge.clj.html index 0d75b13..7f17b26 100644 --- a/docs/cloverage/walkmap/edge.clj.html +++ b/docs/cloverage/walkmap/edge.clj.html @@ -23,7 +23,7 @@ 006              [walkmap.polygon :refer [polygon?]]
- 007              [walkmap.vertex :refer [ensure3d vertex?]])) + 007              [walkmap.vertex :refer [ensure2d ensure3d vertex vertex= vertex?]]))
008   @@ -119,100 +119,457 @@ 038  
- 039  (defn unit-vector + 039  (defn centre
- 040    "Return an vertex parallel to `e` starting from the coordinate origin. Two + 040    "Return the vertex that represents the centre of this `edge`."
- 041    edges which are parallel will have the same unit vector." -
- - 042    [e] -
- - 043    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} -
- - 044          l (length e')] -
- - 045      (reduce -
- - 046        merge -
- - 047        {} -
- - 048        (map -
- - 049          (fn [k] -
- - 050            {k (/ (- (k (:end e')) (k (:start e'))) l)}) -
- - 051          [:x :y :z])))) -
- - 052   -
- - 053  (defn parallel? -
- - 054    "True if all `edges` passed are parallel with one another." -
- - 055    ;; TODO: this bears being wary about, dealing with floating point arithmetic. -
- - 056    ;; Keep an eye out for spurious errors. -
- - 057    [& edges] -
- - 058    (let [uvs (map unit-vector edges)] -
- - 059      (every? + 041    [edge]
- 060        #(= % (first uvs)) + 042    (let [s (ensure3d (:start edge)) +
+ + 043          e (ensure3d (:end edge))] +
+ + 044      (vertex +
+ + 045        (+ (:x s) (/ (- (:x e) (:x s)) 2)) +
+ + 046        (+ (:y s) (/ (- (:y e) (:y s)) 2)) +
+ + 047        (+ (:z s) (/ (- (:z e) (:z s)) 2))))) +
+ + 048   +
+ + 049  (defn unit-vector +
+ + 050    "Return an vertex parallel to `e` starting from the coordinate origin. Two +
+ + 051    edges which are parallel will have the same unit vector." +
+ + 052    [e] +
+ + 053    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))}
- 061        (rest uvs)))) + 054          l (length e')] +
+ + 055      (reduce +
+ + 056        merge +
+ + 057        {} +
+ + 058        (map +
+ + 059          (fn [k] +
+ + 060            {k (/ (- (k (:end e')) (k (:start e'))) l)}) +
+ + 061          [:x :y :z]))))
062  
- 063  (defn collinear? + 063  (defn parallel?
- 064    "True if edges `e1` and `e2` are collinear with one another." + 064    "True if all `edges` passed are parallel with one another."
- 065    [e1 e2] + 065    [& edges]
- - 066    (parallel? + + 066    (let [uvs (map unit-vector edges)]
- - 067      e1 + + 067      (every?
- - 068      e2 + + 068        #(vertex= % (first uvs))
- - 069      {:start (:start e1) :end (:start e2)})) + + 069        (rest uvs))))
070  
+ + 071  (defn collinear? +
+ + 072    "True if edges `e1` and `e2` are collinear with one another." +
+ + 073    [e1 e2] +
+ + 074    (parallel? +
+ + 075      e1 +
+ + 076      e2 +
+ + 077      (if (vertex= (:start e1) (:start e2)) +
+ + 078        {:start (:start e1) :end (:end e2)} +
+ + 079        {:start (:start e1) :end (:start e2)}))) +
+ + 080   +
+ + 081  (defn collinear2d? +
+ + 082    "True if the projections of edges `e1`, `e2` onto the x, y plane are +
+ + 083    collinear." +
+ + 084    [e1 e2] +
+ + 085    (collinear? {:start (ensure2d (:start e1)) :end (ensure2d (:end e1))} +
+ + 086                {:start (ensure2d (:start e2)) :end (ensure2d (:end e2))})) +
+ + 087   +
+ + 088  (defn minimaxd +
+ + 089    "Apply function `f` to `coord` of the vertices at start and end of `edge` +
+ + 090    and return the result. Intended use case is `f` = `min` or `max`, `coord` +
+ + 091    is `:x`, `:y` or `:z`. No checks are made for sane arguments." +
+ + 092    [edge coord f] +
+ + 093    (apply f (list (coord (:start edge)) (coord (:end edge))))) +
+ + 094   +
+ + 095  (defn on? +
+ + 096    "True if the vertex `v` is on the edge `e`." +
+ + 097    [e v] +
+ + 098    (let [p (ensure3d (:start e)) +
+ + 099          q (ensure3d v) +
+ + 100          r (ensure3d (:end e))] +
+ + 101      (and +
+ + 102        (collinear? (edge p q) (edge q r)) +
+ + 103        (<= (:x q) (max (:x p) (:x r))) +
+ + 104        (>= (:x q) (min (:x p) (:x r))) +
+ + 105        (<= (:y q) (max (:y p) (:y r))) +
+ + 106        (>= (:y q) (min (:y p) (:y r))) +
+ + 107        (<= (:z q) (max (:z p) (:z r))) +
+ + 108        (>= (:z q) (min (:z p) (:z r)))))) +
+ + 109   +
+ + 110  (defn on2d? +
+ + 111    "True if vertex `v` is on edge `e` when projected onto the x, y plane." +
+ + 112    [e v] +
+ + 113    (on? (edge (ensure2d (:start e)) (ensure2d (:end e))) v)) +
+ + 114   +
+ + 115  (defn overlaps2d? +
+ + 116    "True if the recangle in the x,y plane bisected by edge `e1` overlaps that +
+ + 117    bisected by edge `e2`. It is an error if either `e1` or `e2` is not an edge." +
+ + 118    [e1 e2] +
+ + 119    (when (and (edge? e1) (edge? e2)) +
+ + 120      (and +
+ + 121        (> (minimaxd e1 :x max) (minimaxd e2 :x min)) +
+ + 122        (< (minimaxd e1 :x min) (minimaxd e2 :x max)) +
+ + 123        (> (minimaxd e1 :y max) (minimaxd e2 :y min)) +
+ + 124        (< (minimaxd e1 :y min) (minimaxd e2 :y max))))) +
+ + 125   +
+ + 126  ;; Don't think I need this. +
+ + 127  ;; (defn orientation +
+ + 128  ;;   "Determine whether the ordered sequence of vertices `p`, `q` and `r` run +
+ + 129  ;;   clockwise, collinear or anticlockwise in the x,y plane." +
+ + 130  ;;   [p q r] +
+ + 131  ;;   (let [v (- (* (- (:y q) (:y p)) (- (:x r) (:x q))) +
+ + 132  ;;             (* (- (:x q) (:x p)) (- (:y r) (:y q))))] +
+ + 133  ;;     (cond +
+ + 134  ;;       (zero? v) :collinear +
+ + 135  ;;       (pos? v) :clockwise +
+ + 136  ;;       :else +
+ + 137  ;;       :anticlockwise))) +
+ + 138   +
+ + 139  (defn intersection2d +
+ + 140    "The probability of two lines intersecting in 3d space is low, and actually +
+ + 141    that is mostly not something we're interested in. We're interested in +
+ + 142    intersection in the `x,y` plane. This function returns a vertex representing +
+ + 143    a point vertically over the intersection of edges `e1`, `e2` in the `x,y` +
+ + 144    plane, whose `z` coordinate is +
+ + 145   +
+ + 146    * 0 if both edges are 2d (i.e. have missing or zero `z` coordinates); +
+ + 147    * if one edge is 2d, then the point on the other edge over the intersection; +
+ + 148    * otherwise, the average of the z coordinates of the points on the two +
+ + 149    edges over the intersection. +
+ + 150   +
+ + 151    If no such intersection exists, `nil` is returned. +
+ + 152   +
+ + 153    It is an error, and an exception will be thrown, if either `e1` or `e2` is +
+ + 154    not an edge." +
+ + 155    [e1 e2] +
+ + 156    (if (and (edge? e1) (edge? e2)) +
+ + 157      (when +
+ + 158        (overlaps2d? e1 e2) ;; relatively cheap check +
+ + 159        (if +
+ + 160          (collinear2d? e1 e2) +
+ + 161          ;; any point within the overlap will do, but we'll pick the end of e1 +
+ + 162          ;; which is on e2 +
+ + 163          (if (on2d? e2 (:start e1)) (:start e1) (:end e1)) +
+ + 164          ;; blatantly stolen from +
+ + 165          ;; https://gist.github.com/cassiel/3e725b49670356a9b936 +
+ + 166          (let [x1 (:x (:start e1)) +
+ + 167                x2 (:x (:end e1)) +
+ + 168                x3 (:x (:start e2)) +
+ + 169                x4 (:x (:end e2)) +
+ + 170                y1 (:y (:start e1)) +
+ + 171                y2 (:y (:end e1)) +
+ + 172                y3 (:y (:start e2)) +
+ + 173                y4 (:y (:end e2)) +
+ + 174                denom (- (* (- x1 x2) (- y3 y4)) +
+ + 175                         (* (- y1 y2) (- x3 x4))) +
+ + 176                x1y2-y1x2 (- (* x1 y2) (* y1 x2)) +
+ + 177                x3y4-y3x4 (- (* x3 y4) (* y3 x4)) +
+ + 178                px-num (- (* x1y2-y1x2 (- x3 x4)) +
+ + 179                          (* (- x1 x2) x3y4-y3x4)) +
+ + 180                py-num (- (* x1y2-y1x2 (- y3 y4)) +
+ + 181                          (* (- y1 y2) x3y4-y3x4)) +
+ + 182                result (when-not (zero? denom) +
+ + 183                         (vertex (/ px-num denom) (/ py-num denom)))] +
+ + 184            (when (and result (on2d? e1 result) (on2d? e2 result)) result)))) +
+ + 185      (throw (IllegalArgumentException. +
+ + 186               (str +
+ + 187                 "Both `e1` and `e2` must be edges." +
+ + 188                 (map #(or (:kind %) (type %)) [e1 e2])))))) +
+ + 189   +
diff --git a/docs/cloverage/walkmap/geometry.clj.html b/docs/cloverage/walkmap/geometry.clj.html index c43eb85..c2ae11e 100644 --- a/docs/cloverage/walkmap/geometry.clj.html +++ b/docs/cloverage/walkmap/geometry.clj.html @@ -11,70 +11,49 @@ 002    (:require [clojure.math.combinatorics :as combo]

- 003              [clojure.math.numeric-tower :as m] -
- - 004              [walkmap.edge :as e] -
- - 005              [walkmap.path :refer [path? polygon->path]] -
- - 006              [walkmap.polygon :refer [polygon?]] -
- - 007              [walkmap.vertex :as v])) + 003              [clojure.math.numeric-tower :as m]))
- 008   + 004  
- 009  (defn on? + 005  (defn =ish
- 010    "True if the vertex `v` is on the edge `e`." + 006    "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to
- 011    [e v] + 007    within `tolerance` (defaults to one part in a million)."
- - 012    (let [p (v/ensure3d (:start e)) + + 008    ([n1 n2]
- - 013          q (v/ensure3d v) + + 009     (if (and (number? n1) (number? n2))
- - 014          r (v/ensure3d (:end e))] + + 010       (let [m (m/abs (min n1 n2))
- - 015      (and + + 011             t (if (zero? m) 0.000001 (* 0.000001 m))]
- - 016        (e/collinear? p q r) + + 012         (=ish n1 n2 t))
- - 017        (<= (:x q) (max (:x p) (:x r))) + + 013       (= n1 n2)))
- - 018        (>= (:x q) (min (:x p) (:x r))) + + 014    ([n1 n2 tolerance]
- - 019        (<= (:y q) (max (:y p) (:y r))) + + 015     (if (and (number? n1) (number? n2))
- - 020        (>= (:y q) (min (:y p) (:y r))) + + 016       (< (m/abs (- n1 n2)) tolerance)
- - 021        (<= (:z q) (max (:z p) (:z r))) -
- - 022        (>= (:z q) (min (:z p) (:z r)))))) -
- - 023   -
- - 024   + + 017       (= n1 n2))))
diff --git a/docs/cloverage/walkmap/path.clj.html b/docs/cloverage/walkmap/path.clj.html index 247af41..025d1dc 100644 --- a/docs/cloverage/walkmap/path.clj.html +++ b/docs/cloverage/walkmap/path.clj.html @@ -35,7 +35,7 @@ 010    "True if `o` satisfies the conditions for a path. A path shall be a map

- 011    having the key `:nodes`, whose value shall be a sequence of vertices as + 011    having the key `:vertices`, whose value shall be a sequence of vertices as
012    defined in `walkmap.vertex`." @@ -47,7 +47,7 @@ 014    (let
- 015      [v (:nodes o)] + 015      [v (:vertices o)]
016      (and @@ -86,7 +86,7 @@ 027      (every? vertex? vertices)
- 028      {:nodes vertices :id (keyword (gensym "path")) :kind :path} + 028      {:vertices vertices :id (keyword (gensym "path")) :kind :path}
029      (throw (IllegalArgumentException. "Each item on path must be a vertex.")))) @@ -125,7 +125,7 @@ 040      (polygon? o)
- 041      (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) + 041      (assoc (dissoc o :vertices) :kind :path :vertices (concat (:vertices o) (list (first (:vertices o)))))
042      (throw (IllegalArgumentException. "Not a polygon!")))) @@ -175,62 +175,68 @@ 057        (cons
+ + 058          ;; TODO: think about: when constructing an edge from a path, should the +
+ + 059          ;; constructed edge be tagged with the tags of the path? +
- 058          (e/edge (first o) (rest o)) + 060          (e/edge (first o) (rest o))
- 059          (path->edges (rest o)))) + 061          (path->edges (rest o))))
- 060      (path? o) + 062      (path? o)
- 061      (path->edges (:nodes o)) + 063      (path->edges (:vertices o))
- 062      :else + 064      :else
- 063      (throw (IllegalArgumentException. + 065      (throw (IllegalArgumentException.
- 064               "Not a path or sequence of vertices!")))) + 066               "Not a path or sequence of vertices!"))))
- 065   + 067  
- 066  (defn length + 068  (defn length
- 067    "Return the length of this path, in metres. **Note that** + 069    "Return the length of this path, in metres. **Note that**
- 068    1. This is not the same as the distance from the start to the end of the + 070    1. This is not the same as the distance from the start to the end of the
- 069    path, which, except for absolutely straight paths, will be shorter; + 071    path, which, except for absolutely straight paths, will be shorter;
- 070    2. It is not even quite the same as the length of the path *as rendered*, + 072    2. It is not even quite the same as the length of the path *as rendered*,
- 071    since paths will generally be rendered as spline curves." + 073    since paths will generally be rendered as spline curves."
- 072    [path] + 074    [path]
- 073    (if + 075    (if
- 074      (path? path) + 076      (path? path)
- 075      (reduce + (map e/length (path->edges path))) + 077      (reduce + (map e/length (path->edges path)))
- 076      (throw (IllegalArgumentException. "Not a path!")))) + 078      (throw (IllegalArgumentException. "Not a path!"))))
diff --git a/docs/cloverage/walkmap/polygon.clj.html b/docs/cloverage/walkmap/polygon.clj.html index 0be1cbd..2bb0272 100644 --- a/docs/cloverage/walkmap/polygon.clj.html +++ b/docs/cloverage/walkmap/polygon.clj.html @@ -37,7 +37,7 @@ 011      [v (:vertices o)]
- + 012      (and
@@ -49,14 +49,17 @@ 015        (every? vertex? v)
- - 016        (or (nil? (:kind o)) (= (:kind o) :polygon))))) + + 016        (:id o)
- - 017   + + 017        (or (nil? (:kind o)) (= (:kind o) :polygon)))))
018  
+ + 019   +
diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html index 61b4549..0684c28 100644 --- a/docs/cloverage/walkmap/stl.clj.html +++ b/docs/cloverage/walkmap/stl.clj.html @@ -26,427 +26,556 @@ 007              [taoensso.timbre :as l :refer [info error spy]]

- 008              [walkmap.polygon :refer [polygon?]] + 008              [walkmap.edge :as e]
- 009              [walkmap.vertex :as v]) + 009              [walkmap.polygon :refer [polygon?]]
- 010    (:import org.clojars.smee.binary.core.BinaryIO + 010              [walkmap.tag :refer [tag]]
- 011             java.io.DataInput)) + 011              [walkmap.vertex :as v]) +
+ + 012    (:import org.clojars.smee.binary.core.BinaryIO +
+ + 013             java.io.DataInput))
- 012   + 014  
- 013  (defn stl? + 015  (defn stl?
- 014    "True if `o` is recogniseable as an STL structure. An STL structure must + 016    "True if `o` is recogniseable as an STL structure. An STL structure must
- 015    have a key `:facets`, whose value must be a sequence of polygons; and + 017    have a key `:facets`, whose value must be a sequence of polygons; and
- 016    may have a key `:header` whose value should be a string, and/or a key + 018    may have a key `:header` whose value should be a string, and/or a key
- 017    `:count`, whose value should be a positive integer. + 019    `:count`, whose value should be a positive integer.
- 018   + 020  
- 019    If `verify-count?` is passed and is not `false`, verify that the value of + 021    If `verify-count?` is passed and is not `false`, verify that the value of
- 020    the `:count` header is equal to the number of facets." + 022    the `:count` header is equal to the number of facets."
- 021    ([o] + 023    ([o]
- 022     (stl? o false)) + 024     (stl? o false))
- 023    ([o verify-count?] + 025    ([o verify-count?]
- 024     (and + 026     (and
- 025       (map? o) + 027       (map? o)
- 026       (:facets o) + 028       (:facets o)
- 027       (every? polygon? (:facets o)) + 029       (every? polygon? (:facets o))
- 028       (if (:header o) (string? (:header o)) true) + 030       (if (:header o) (string? (:header o)) true)
- 029       (if (:count o) (integer? (:count o)) true) + 031       (if (:count o) (integer? (:count o)) true)
- 030       (or (nil? (:kind o)) (= (:kind o) :stl)) + 032       (or (nil? (:kind o)) (= (:kind o) :stl))
- 031       (if verify-count? (= (:count o) (count (:facets o))) true)))) + 033       (if verify-count? (= (:count o) (count (:facets o))) true))))
- 032   + 034  
- 033  (def vect + 035  (def vect
- 034    "A codec for vectors within a binary STL file." + 036    "A codec for vectors within a binary STL file."
- 035    (b/ordered-map + 037    (b/ordered-map
- 036      :x :float-le + 038      :x :float-le
- 037      :y :float-le + 039      :y :float-le
- 038      :z :float-le)) + 040      :z :float-le))
- 039   + 041  
- 040  (def facet + 042  (def facet
- 041    "A codec for a facet (triangle) within a binary STL file." + 043    "A codec for a facet (triangle) within a binary STL file."
- 042    (b/ordered-map + 044    (b/ordered-map
- 043      :normal vect + 045      :normal vect
- 044      :vertices [vect vect vect] + 046      :vertices [vect vect vect]
- 045      :abc :ushort-le)) + 047      :abc :ushort-le))
- 046   + 048  
- 047  (def binary-stl + 049  (def binary-stl
- 048    "A codec for binary STL files" + 050    "A codec for binary STL files"
- 049    (b/ordered-map + 051    (b/ordered-map
- 050     :header (b/string "ISO-8859-1" :length 80) ;; for the time being we neither know nor care what's in this. + 052     :header (b/string "ISO-8859-1" :length 80) ;; for the time being we neither know nor care what's in this.
- 051     :count :uint-le + 053     :count :uint-le
- 052     :facets (b/repeated facet))) + 054     :facets (b/repeated facet)))
- 053   + 055  
- 054  (defn canonicalise + 056  (defn centre
- 055    "Objects read in from STL won't have all the keys/values we need them to have." + 057    "Return a canonicalised `facet` (i.e. a triangular polygon) with an added
- 056    [o] + 058    key `:centre` whose value represents the centre of this facet in 3 +
+ + 059    dimensions. This only works for triangles, so is here not in +
+ + 060    `walkmap.polygon`. It is an error (although no exception is currently +
+ + 061    thrown) if the object past is not a triangular polygon." +
+ + 062    [facet] +
+ + 063    (let [vs (:vertices facet) +
+ + 064          v1 (first vs) +
+ + 065          opposite (e/edge (nth vs 1) (nth vs 2)) +
+ + 066          oc (e/centre opposite)] +
+ + 067      (assoc +
+ + 068        facet +
+ + 069        :centre +
+ + 070        (v/vertex +
+ + 071          (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3)) +
+ + 072          (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3)) +
+ + 073          (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3)))))) +
+ + 074   +
+ + 075  (defn canonicalise +
+ + 076    "Objects read in from STL won't have all the keys/values we need them to have. +
+ + 077    `o` may be a map (representing a facet or a vertex), or a sequence of such maps; +
+ + 078    if it isn't recognised it is at present just returned unchanged. `map-kind`, if +
+ + 079    passed, must be a keyword indicating the value represented by the `z` axis +
+ + 080    (defaults to `:height`). It is an error, and an exception will be thrown, if +
+ + 081    `map-kind` is not a keyword." +
+ + 082    ([o] (canonicalise o :height)) +
+ + 083    ([o map-kind] +
+ + 084     (when-not +
+ + 085       (keyword? map-kind) +
+ + 086       (throw (IllegalArgumentException. +
+ + 087                (subs (str "Must be a keyword: " (or map-kind "nil")) 0 80))))
- 057    (cond + 088     (cond
- - 058      (and (coll? o) (not (map? o))) (map canonicalise o) + + 089       (and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o)
- 059      ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?` + 090       ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?`
- 060      (:facets o) (assoc o + 091       (:facets o) (assoc o
- 061                 :kind :stl + 092                     :kind :stl
- 062                 :id (or (:id o) (keyword (gensym "stl"))) -
- - 063                 :facets (canonicalise (:facets o))) -
- - 064      ;; if it has :vertices it's a polygon, but it doesn't yet conform to `polygon?` -
- - 065      (:vertices o) (assoc o -
- - 066                      :id (or (:id o) (keyword (gensym "poly"))) -
- - 067                      :kind :polygon -
- - 068                      :vertices (canonicalise (:vertices o))) -
- - 069      ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?` + 093                     :id (or (:id o) (keyword (gensym "stl")))
- 070      (:x o) (v/canonicalise o) + 094                     :facets (canonicalise (:facets o) map-kind))
- 071      ;; shouldn't happen + 095       ;; if it has :vertices it's a polygon, but it doesn't yet conform to `polygon?`
- - 072      :else o)) + + 096       (:vertices o) (centre
- - 073   + + 097                       (tag +
+ + 098                         (assoc o +
+ + 099                           :id (or (:id o) (keyword (gensym "poly"))) +
+ + 100                           :kind :polygon +
+ + 101                           :vertices (canonicalise (:vertices o) map-kind))
- 074  (defn decode-binary-stl + 102                         :facet map-kind))
- 075    "Parse a binary STL file from this `filename` and return an STL structure + 103       ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?` +
+ + 104       (:x o) (v/canonicalise o)
- 076    representing its contents. + 105       ;; shouldn't happen +
+ + 106       :else o)))
- 077   + 107   +
+ + 108  (defn decode-binary-stl
- 078    **NOTE** that we've no way of verifying that the input file is binary STL + 109    "Parse a binary STL file from this `filename` and return an STL structure
- 079    data, if it is not this will run but will return garbage." + 110    representing its contents. `map-kind`, if passed, must be a keyword
- 080    [filename] + 111    indicating the value represented by the `z` axis (defaults to `:height`). +
+ + 112    It is an error, and an exception will be thrown, if `map-kind` is not a +
+ + 113    keyword. +
+ + 114   +
+ + 115    **NOTE** that we've no way of verifying that the input file is binary STL +
+ + 116    data, if it is not this will run but will return garbage." +
+ + 117    ([filename]
- 081    (let [in (io/input-stream filename)] + 118     (decode-binary-stl filename :height))
- - 082      (canonicalise (b/decode binary-stl in)))) + + 119    ([filename map-kind] +
+ + 120     (when-not +
+ + 121       (keyword? map-kind) +
+ + 122       (throw (IllegalArgumentException. +
+ + 123                (subs (str "Must be a keyword: " (or map-kind "nil")) 0 80)))) +
+ + 124     (let [in (io/input-stream filename)] +
+ + 125       (canonicalise (b/decode binary-stl in) map-kind))))
- 083   + 126  
- 084  (defn- vect->str [prefix v] + 127  (defn- vect->str [prefix v]
- 085    (str prefix " " (:x v) " " (:y v) " " (:z v) "\n")) -
- - 086   -
- - 087  (defn- facet2str [tri] -
- - 088    (str -
- - 089      (vect->str "facet normal" (:normal tri)) -
- - 090      "outer loop\n" -
- - 091      (apply str -
- - 092             (map -
- - 093               #(vect->str "vertex" %) -
- - 094               (:vertices tri))) -
- - 095      "endloop\nendfacet\n")) -
- - 096   -
- - 097  (defn stl->ascii -
- - 098    "Return as a string an ASCII rendering of the `stl` structure." -
- - 099    ([stl] -
- - 100     (stl->ascii stl "unknown")) -
- - 101    ([stl solidname] -
- - 102     (str -
- - 103       "solid " -
- - 104       solidname -
- - 105       (s/trim (:header stl)) -
- - 106       "\n" -
- - 107       (apply -
- - 108         str -
- - 109         (map -
- - 110           facet2str -
- - 111           (:facets stl))) -
- - 112       "endsolid " -
- - 113       solidname -
- - 114       "\n"))) -
- - 115   -
- - 116  (defn write-ascii-stl -
- - 117    "Write an `stl` structure as read by `decode-binary-stl` to this -
- - 118    `filename` as ASCII encoded STL." -
- - 119    ([filename stl] -
- - 120     (let [b (fs/base-name filename true)] -
- - 121       (write-ascii-stl -
- - 122         filename stl -
- - 123         (subs b 0 (or (s/index-of b ".") (count b)))))) -
- - 124    ([filename stl solidname] -
- - 125     (l/debug "Solid name is " solidname) -
- - 126     (spit -
- - 127       filename -
- - 128       (stl->ascii stl solidname)))) + 128    (str prefix " " (:x v) " " (:y v) " " (:z v) "\n"))
129  
- - 130  (defn binary-stl-to-ascii -
- - 131    "Convert the binary STL file indicated by `in-filename`, and write it to -
- - 132    `out-filename`, if specified; otherwise, to a file with the same basename -
- - 133    as `in-filename` but the extension `.ascii.stl`." -
- - 134    ([in-filename] + + 130  (defn- facet2str [tri]
- 135     (let [[_ ext] (fs/split-ext in-filename)] -
- - 136       (binary-stl-to-ascii -
- - 137         in-filename -
- - 138         (str -
- - 139           (subs -
- - 140             in-filename -
- - 141             0 -
- - 142             (or -
- - 143               (s/last-index-of in-filename ".") -
- - 144               (count in-filename))) -
- - 145           ".ascii" -
- - 146           ext)))) -
- - 147    ([in-filename out-filename] + 131    (str
- 148     (write-ascii-stl out-filename (decode-binary-stl in-filename)))) + 132      (vect->str "facet normal" (:normal tri)) +
+ + 133      "outer loop\n" +
+ + 134      (apply str +
+ + 135             (map +
+ + 136               #(vect->str "vertex" %) +
+ + 137               (:vertices tri))) +
+ + 138      "endloop\nendfacet\n")) +
+ + 139   +
+ + 140  (defn stl->ascii +
+ + 141    "Return as a string an ASCII rendering of the `stl` structure." +
+ + 142    ([stl] +
+ + 143     (stl->ascii stl "unknown")) +
+ + 144    ([stl solidname] +
+ + 145     (str +
+ + 146       "solid " +
+ + 147       solidname +
+ + 148       (s/trim (:header stl)) +
+ + 149       "\n" +
+ + 150       (apply +
+ + 151         str +
+ + 152         (map +
+ + 153           facet2str +
+ + 154           (:facets stl))) +
+ + 155       "endsolid " +
+ + 156       solidname +
+ + 157       "\n"))) +
+ + 158   +
+ + 159  (defn write-ascii-stl +
+ + 160    "Write an `stl` structure as read by `decode-binary-stl` to this +
+ + 161    `filename` as ASCII encoded STL." +
+ + 162    ([filename stl] +
+ + 163     (let [b (fs/base-name filename true)] +
+ + 164       (write-ascii-stl +
+ + 165         filename stl +
+ + 166         (subs b 0 (or (s/index-of b ".") (count b)))))) +
+ + 167    ([filename stl solidname] +
+ + 168     (l/debug "Solid name is " solidname) +
+ + 169     (spit +
+ + 170       filename +
+ + 171       (stl->ascii stl solidname)))) +
+ + 172   +
+ + 173  (defn binary-stl-to-ascii +
+ + 174    "Convert the binary STL file indicated by `in-filename`, and write it to +
+ + 175    `out-filename`, if specified; otherwise, to a file with the same basename +
+ + 176    as `in-filename` but the extension `.ascii.stl`." +
+ + 177    ([in-filename] +
+ + 178     (let [[_ ext] (fs/split-ext in-filename)] +
+ + 179       (binary-stl-to-ascii +
+ + 180         in-filename +
+ + 181         (str +
+ + 182           (subs +
+ + 183             in-filename +
+ + 184             0 +
+ + 185             (or +
+ + 186               (s/last-index-of in-filename ".") +
+ + 187               (count in-filename))) +
+ + 188           ".ascii" +
+ + 189           ext)))) +
+ + 190    ([in-filename out-filename] +
+ + 191     (write-ascii-stl out-filename (decode-binary-stl in-filename))))
diff --git a/docs/cloverage/walkmap/superstructure.clj.html b/docs/cloverage/walkmap/superstructure.clj.html index 3c16e1c..e2963e8 100644 --- a/docs/cloverage/walkmap/superstructure.clj.html +++ b/docs/cloverage/walkmap/superstructure.clj.html @@ -28,203 +28,236 @@ 008  
- - 009  (defn index-vertex + + 009  ;; TODO: Think about reification/dereification. How can we cull a polygon, if
- 010    "Return a superstructure like `s` in which object `o` is indexed by vertex + 010  ;; some vertices still index it? I *think* that what's needed is that when
- 011    `v`. It is an error (and an exception may be thrown) if + 011  ;; we store something in the superstructure, we replace all its vertices (and +
+ + 012  ;; other dependent structures, if any with their ids - as well as, obviously, +
+ + 013  ;; adding/merging those vertices/dependent structures into the superstructure +
+ + 014  ;; as first class objects in themselves. That means, for each identified thing, +
+ + 015  ;; the superstructure only contains one copy of it. +
+ + 016  ;; +
+ + 017  ;; The question then is, when we want to do things with those objects, do we +
+ + 018  ;; exteract a copy with its dependent structures fixed back up (reification), +
+ + 019  ;; or do we indirect through the superstructure every time we want to access +
+ + 020  ;; them? In a sense, the copy in the superstructure is the 'one true copy', +
+ + 021  ;; but it may become very difficult then to have one true copy of the +
+ + 022  ;; superstructure - unless we replace the superstructure altogether with a +
+ + 023  ;; database, which may be the Right Thing To Do.
- 012   + 024   +
+ + 025  (defn index-vertex
- 013    1. `s` is not a map; + 026    "Return a superstructure like `s` in which object `o` is indexed by vertex
- 014    2. `o` is not a map; + 027    `v`. It is an error (and an exception may be thrown) if +
+ + 028  
- 015    3. `o` does not have a value for the key `:id`; + 029    1. `s` is not a map;
- 016    4. `v` is not a vertex." + 030    2. `o` is not a map;
- 017    ;; two copies of the same vertex are not identical enough to one another + 031    3. `o` does not have a value for the key `:id`;
- 018    ;; to be used as keys in a map. So our vertices need to have ids, and we need + 032    4. `v` is not a vertex."
- 019    ;; to key the vertex-index by vertex ids. -
- - 020    ;; TODO: BUT WE CANNOT USE GENSYMED ids, because two vertices with the same -
- - 021    ;; vertices must have the same id! -
- - 022    [s o v] + 033    [s o v]
- 023    (if-not (v/vertex? o) + 034    (if-not (v/vertex? o)
- 024      (if (:id o) + 035      (if (:id o)
- 025        (if (v/vertex? v) + 036        (if (v/vertex? v)
- 026          (let [vi (or (:vertex-index s) {}) + 037          (let [vi (or (:vertex-index s) {})
- 027                current (or (vi (:id v)) {})] + 038                current (or (vi (:id v)) {})]
- 028            ;; deep-merge doesn't merge sets, only maps; so at this + 039            ;; deep-merge doesn't merge sets, only maps; so at this
- 029            ;; stage we need to build a map. + 040            ;; stage we need to build a map.
- 030            (assoc vi (:id v) (assoc current (:id o) (:id v)))) + 041            (assoc vi (:id v) (assoc current (:id o) (:id v))))
- 031          (throw (IllegalArgumentException. "Not a vertex: " v))) + 042          (throw (IllegalArgumentException. "Not a vertex: " v)))
- 032        (throw (IllegalArgumentException. (subs (str "No `:id` value: " o) 0 80)))) + 043        (throw (IllegalArgumentException. (subs (str "No `:id` value: " o) 0 80))))
- 033      ;; it shouldn't actually be an error to try to index a vertex, but it + 044      ;; it shouldn't actually be an error to try to index a vertex, but it
- 034      ;; also isn't useful to do so, so I'd be inclined to ignore it. + 045      ;; also isn't useful to do so, so I'd be inclined to ignore it.
- 035      (:vertex-index s))) + 046      (:vertex-index s)))
- 036   + 047  
- 037  (defn index-vertices + 048  (defn index-vertices
- 038    "Return a superstructure like `s` in which object `o` is indexed by its + 049    "Return a superstructure like `s` in which object `o` is indexed by its
- 039    vertices. It is an error (and an exception may be thrown) if + 050    vertices. It is an error (and an exception may be thrown) if
- 040   + 051  
- 041    1. `s` is not a map; + 052    1. `s` is not a map;
- 042    2. `o` is not a map; + 053    2. `o` is not a map;
- 043    3. `o` does not have a value for the key `:id`." + 054    3. `o` does not have a value for the key `:id`."
- 044    [s o] + 055    [s o]
- 045    (assoc + 056    (assoc
- 046      s + 057      s
- 047      :vertex-index + 058      :vertex-index
- 048      (reduce + 059      (reduce
- 049        u/deep-merge + 060        u/deep-merge
- 050        (map + 061        (map
- 051          #(index-vertex s o %) + 062          #(index-vertex s o %)
- 052          (u/vertices o))))) + 063          (u/vertices o)))))
- 053   + 064  
- 054  (defn add-to-superstructure + 065  (defn add-to-superstructure
- 055    "Return a superstructure like `s` with object `o` added. If `o` is a collection, + 066    "Return a superstructure like `s` with object `o` added. If `o` is a collection,
- 056    return a superstructure like `s` with each element of `o` added. If only one + 067    return a superstructure like `s` with each element of `o` added. If only one
- 057    argument is supplied it will be assumed to represent `o` and a new + 068    argument is supplied it will be assumed to represent `o` and a new
- 058    superstructure will be returned. + 069    superstructure will be returned.
- 059   + 070  
- 060    It is an error (and an exception may be thrown) if + 071    It is an error (and an exception may be thrown) if
- 061   + 072  
- 062    1. `s` is not a map; + 073    1. `s` is not a map;
- 063    2. `o` is not a map, or a sequence of maps." + 074    2. `o` is not a map, or a sequence of maps."
- 064    ([o] + 075    ([o]
- 065     (add-to-superstructure {} o)) + 076     (add-to-superstructure {} o))
- 066    ([s o] + 077    ([s o]
- 067    (cond + 078    (cond
- 068      (map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))] + 079      (map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))]
- 069                 (index-vertices (assoc s (:id o') o') o')) + 080                 (index-vertices (assoc s (:id o') o') o'))
- 070      (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o)) + 081      (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o))
- 071      (nil? o) o + 082      (nil? o) o
- 072      :else + 083      :else
- 073      (throw (IllegalArgumentException. (str "Don't know how to index " (or (type o) "nil"))))))) + 084      (throw (IllegalArgumentException. (str "Don't know how to index " (or (type o) "nil")))))))
- 074   + 085  
diff --git a/docs/cloverage/walkmap/tag.clj.html b/docs/cloverage/walkmap/tag.clj.html index 53bb926..a395271 100644 --- a/docs/cloverage/walkmap/tag.clj.html +++ b/docs/cloverage/walkmap/tag.clj.html @@ -89,7 +89,7 @@ 028    "Return an object like this `object` but with these `tags` added to its tags,

- 029    if they are not already present.It is an error (and an exception will be + 029    if they are not already present. It is an error (and an exception will be
030    thrown) if diff --git a/docs/cloverage/walkmap/utils.clj.html b/docs/cloverage/walkmap/utils.clj.html index 44beefb..235c672 100644 --- a/docs/cloverage/walkmap/utils.clj.html +++ b/docs/cloverage/walkmap/utils.clj.html @@ -11,76 +11,67 @@ 002    "Miscellaneous utility functions."
- 003    (:require [walkmap.path :as p] + 003    (:require [clojure.math.numeric-tower :as m]
- 004              [walkmap.polygon :as q] + 004              [walkmap.path :as p]
- 005              [walkmap.vertex :as v])) + 005              [walkmap.polygon :as q] +
+ + 006              [walkmap.vertex :as v]))
- 006   + 007  
- 007  (defn deep-merge + 008  (defn deep-merge
- 008    "Recursively merges maps. If vals are not maps, the last value wins." + 009    "Recursively merges maps. If vals are not maps, the last value wins."
- 009    ;; TODO: not my implementation, not sure I entirely trust it. + 010    ;; TODO: not my implementation, not sure I entirely trust it.
- 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 vertices + 016  (defn vertices
- 016    "If `o` is an object with vertices, return those vertices, else nil." + 017    "If `o` is an object with vertices, return those vertices, else nil."
- 017    ;; TODO: it's possibly a design mistake that I'm currently distinguishing -
- - 018    ;; between polygons and paths on the basis that one has `:vertices` and -
- - 019    ;; the other has `:nodes`. Possibly it would be better to have a key -
- - 020    ;; `:closed` which was `true` for polygons, `false` (or missing) for -
- - 021    ;; paths. -
- - 022    [o] + 018    [o]
- 023    (cond + 019    (cond
- 024      (v/vertex? o) (list o) + 020      (v/vertex? o) (list o)
- 025      (q/polygon? o) (:vertices o) + 021      (q/polygon? o) (:vertices o)
- 026      (p/path? o) (:nodes o))) + 022      (p/path? o) (:vertices o))) +
+ + 023  
diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html index c5c0170..e9b80e8 100644 --- a/docs/cloverage/walkmap/vertex.clj.html +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -17,238 +17,334 @@ 004    Note that there's no `distance` function here; to find the distance between

- 005    two vertices, create an edge from them and use `walkmap.edge/length`.") + 005    two vertices, create an edge from them and use `walkmap.edge/length`." +
+ + 006    (:require [clojure.math.numeric-tower :as m] +
+ + 007              [clojure.string :as s] +
+ + 008              [walkmap.geometry :refer [=ish]]))
- 006   + 009  
- 007  (defn vertex-key + 010  (defn vertex-key
- 008    "Making sure we get the same key everytime we key a vertex with the same + 011    "Making sure we get the same key everytime we key a vertex with the same
- 009    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally + 012    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
- 010    `:z`." + 013    `:z`; it is an error and an exception will be thrown if `o` does not
- 011    [o] + 014    conform to this specification. +
+ + 015   +
+ + 016    **Note:** these keys can be quite long. No apology is made: it is required +
+ + 017    that the same key can *never* refer to two different locations in space." +
+ + 018    [o] +
+ + 019    (keyword +
+ + 020      (s/replace
- 012    (cond + 021        (cond
- - 013      (and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}")) -
- - 014      (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}")) -
- - 015      :else (throw (IllegalArgumentException. "Not a vertex.")))) -
- - 016   -
- - 017  (defn vertex? -
- - 018    "True if `o` satisfies the conditions for a vertex. That is, essentially, -
- - 019    that it must rerpresent a two- or three- dimensional vector. A vertex is -
- - 020    shall be a map having at least the keys `:x` and `:y`, where the value of -
- - 021    those keys is a number. If the key `:z` is also present, its value must also -
- - 022    be a number. -
- - 023   -
- - 024    The name  `vector?` was not used as that would clash with a function of that -
- - 025    name in `clojure.core` whose semantics are entirely different." -
- - 026    [o] -
- - 027    (and -
- - 028      (map? o) -
- - 029      (:id o) -
- - 030      (number? (:x o)) -
- - 031      (number? (:y o)) + + 022          (and (:x o) (:y o) (:z o))
- 032      (or (nil? (:z o)) (number? (:z o))) + 023          (str "vert_" (:x o) "_" (:y o) "_" (:z o))
- - 033      (or (nil? (:kind o)) (= (:kind o) :vertex)))) + + 024          (and (:x o) (:y o)) +
+ + 025          (str "vert_" (:x o) "_" (:y o)) +
+ + 026          :else +
+ + 027          (throw (IllegalArgumentException. +
+ + 028                   (subs (str "Not a vertex: " (or o "nil")) 0 80)))) +
+ + 029        "." +
+ + 030        "-")))
- 034   + 031  
- 035  (defn vertex + 032  (defn vertex?
- 036    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map + 033    "True if `o` satisfies the conditions for a vertex. That is, essentially,
- 037    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`. + 034    that it must rerpresent a two- or three- dimensional vector. A vertex is
- 038    It's not necessary to use this function to create a vertex, but the `:id` + 035    shall be a map having at least the keys `:x` and `:y`, where the value of
- 039    must be present and must be unique." + 036    those keys is a number. If the key `:z` is also present, its value must also
- 040    ([x y] -
- - 041     (let [v {:x x :y y :kind :vertex}] -
- - 042       (assoc v :id (vertex-key v)))) -
- - 043    ([x y z] -
- - 044     (assoc (vertex x y) :z z))) + 037    be a number.
- 045   -
- - 046  (defn canonicalise + 038  
- 047    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, + 039    The name  `vector?` was not used as that would clash with a function of that
- 048    upgrade it to something we will recognise as a vertex." + 040    name in `clojure.core` whose semantics are entirely different."
- 049    [o] + 041    [o]
- - 050    (if -
- - 051      (and + + 042    (and
- 052        (map? o) + 043      (map? o) +
+ + 044      (:id o)
- 053        (number? (:x o)) + 045      (number? (:x o))
- 054        (number? (:y o)) + 046      (number? (:y o)) +
+ + 047      (or (nil? (:z o)) (number? (:z o)))
- 055        (or (nil? (:z o)) (number? (:z o)))) + 048      (or (nil? (:kind o)) (= (:kind o) :vertex)))) +
+ + 049   +
+ + 050  (defn vertex= +
+ + 051    "True if vertices `v1`, `v2` represent the same vertex." +
+ + 052    [v1 v2] +
+ + 053    (every? +
+ + 054      #(=ish (% v1) (% v2)) +
+ + 055      [:x :y :z])) +
+ + 056   +
+ + 057  (defn vertex +
+ + 058    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map +
+ + 059    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`. +
+ + 060    It's not necessary to use this function to create a vertex, but the `:id` +
+ + 061    must be present and must be unique." +
+ + 062    ([x y] +
+ + 063     (let [v {:x x :y y :kind :vertex}] +
+ + 064       (assoc v :id (vertex-key v)))) +
+ + 065    ([x y z]
- 056      (assoc o :kind :vertex :id (vertex-key o)) + 066     (let [v (assoc (vertex x y) :z z)]
- - 057      (throw (IllegalArgumentException. "Not a proto-vertex: must have numeric `:x` and `:y`.")))) + + 067       (assoc v :id (vertex-key v)))))
- 058   + 068  
- 059  (def ensure3d + 069  (defn canonicalise
- 060    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise + 070    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
- 061    return a vertex like `o` but having thie `dflt` value as the value of its + 071    upgrade it to something we will recognise as a vertex."
- 062    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. -
- - 063   -
- - 064    If `o` is not a vertex, throws an exception." -
- - 065    (memoize + 072    [o]
- 066      (fn + 073    (if
- - 067        ([o] + + 074      (and
- - 068         (ensure3d o 0.0)) + + 075        (map? o)
- - 069        ([o dflt] + + 076        (number? (:x o))
- - 070         (cond + + 077        (number? (:y o))
- - 071           (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!")) + + 078        (or (nil? (:z o)) (number? (:z o))))
- - 072           (:z o) o -
- - 073           :else (assoc o :z dflt)))))) -
- - 074   -
- - 075  (def ensure2d -
- - 076    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." -
- - 077    (memoize -
- - 078      (fn [o] + + 079      (assoc o :kind :vertex :id (vertex-key o))
- 079        (if + 080      (throw +
+ + 081        (IllegalArgumentException. +
+ + 082          (subs
- 080          (vertex? o) + 083            (str "Not a proto-vertex: must have numeric `:x` and `:y`: "
- - 081          (assoc o :z 0.0) + + 084                 (or o "nil"))
- - 082          (throw (IllegalArgumentException. "Not a vertex!")))))) + + 085            0 80))))) +
+ + 086   +
+ + 087  (def ensure3d +
+ + 088    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise +
+ + 089    return a vertex like `o` but having thie `dflt` value as the value of its +
+ + 090    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. +
+ + 091   +
+ + 092    If `o` is not a vertex, throws an exception." +
+ + 093    (memoize +
+ + 094      (fn +
+ + 095        ([o] +
+ + 096         (ensure3d o 0.0)) +
+ + 097        ([o dflt] +
+ + 098         (cond +
+ + 099           (not (vertex? o)) (throw +
+ + 100                               (IllegalArgumentException. +
+ + 101                                 (subs (str "Not a vertex: " (or o "nil")) 0 80))) +
+ + 102           (:z o) o +
+ + 103           :else (assoc o :z dflt)))))) +
+ + 104   +
+ + 105  (def ensure2d +
+ + 106    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." +
+ + 107    (memoize +
+ + 108      (fn [o] +
+ + 109        (if +
+ + 110          (vertex? o) +
+ + 111          (assoc o :z 0.0) +
+ + 112          (throw +
+ + 113            (IllegalArgumentException. +
+ + 114              (subs (str "Not a vertex: " (or o "nil")) 0 80)))))))
diff --git a/docs/codox/index.html b/docs/codox/index.html index 766d5d5..b9532b2 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.

      Public variables and functions:

      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.

      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.

      \ 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.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 diff --git a/docs/codox/walkmap.edge.html b/docs/codox/walkmap.edge.html index 53996c4..7e9067b 100644 --- a/docs/codox/walkmap.edge.html +++ b/docs/codox/walkmap.edge.html @@ -1,4 +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.

            collinear?

            (collinear? e1 e2)

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

            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.

            length

            (length e)

            Return the length of the edge e.

            parallel?

            (parallel? & edges)

            True if all edges passed are parallel with one another.

            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.

            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 +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 diff --git a/docs/codox/walkmap.geometry.html b/docs/codox/walkmap.geometry.html index 722174d..9033532 100644 --- a/docs/codox/walkmap.geometry.html +++ b/docs/codox/walkmap.geometry.html @@ -1,3 +1,3 @@ -walkmap.geometry documentation

            walkmap.geometry

            TODO: write docs

            on?

            (on? e v)

            True if the vertex v is on the edge e.

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

            walkmap.geometry

            TODO: write docs

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

            walkmap.path

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

            make-path

            (make-path nodes)

            TODO: write docs

            path?

            (path? o)

            True if o satisfies the conditions for a path. A path shall be a map having the key :nodes, 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.

            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.stl.html b/docs/codox/walkmap.stl.html index bac014e..d35b6c0 100644 --- a/docs/codox/walkmap.stl.html +++ b/docs/codox/walkmap.stl.html @@ -1,5 +1,5 @@ -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)

            Objects read in from STL won’t have all the keys/values we need them to have.

            decode-binary-stl

            (decode-binary-stl filename)

            Parse a binary STL file from this filename and return an STL structure representing its contents.

            -

            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)

            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 diff --git a/docs/codox/walkmap.superstructure.html b/docs/codox/walkmap.superstructure.html index 0876fb7..0497927 100644 --- a/docs/codox/walkmap.superstructure.html +++ b/docs/codox/walkmap.superstructure.html @@ -5,15 +5,15 @@
            1. s is not a map;
            2. o is not a map, or a sequence of maps.
            3. -

            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

            +

            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. o is not a map;
            3. o does not have a value for the key :id;
            4. v is not a vertex.
            5. -

            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

            +

            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. o is not a map;
            3. o does not have a value for the key :id.
            4. -
            \ No newline at end of file + \ No newline at end of file diff --git a/docs/codox/walkmap.tag.html b/docs/codox/walkmap.tag.html index 1c2045c..4ffe270 100644 --- a/docs/codox/walkmap.tag.html +++ b/docs/codox/walkmap.tag.html @@ -1,6 +1,6 @@ -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. any of tags is not a keyword.
            3. diff --git a/docs/codox/walkmap.vertex.html b/docs/codox/walkmap.vertex.html index 77c0205..129bced 100644 --- a/docs/codox/walkmap.vertex.html +++ b/docs/codox/walkmap.vertex.html @@ -1,5 +1,7 @@ -walkmap.vertex documentation

              walkmap.vertex

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

              canonicalise-vertex

              (canonicalise-vertex 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.

              make-vertex

              (make-vertex x y)(make-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.

              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.

              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 diff --git a/src/walkmap/edge.clj b/src/walkmap/edge.clj index 17319ed..df83a5d 100644 --- a/src/walkmap/edge.clj +++ b/src/walkmap/edge.clj @@ -4,7 +4,7 @@ nodes." (:require [clojure.math.numeric-tower :as m] [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :refer [ensure3d vertex?]])) + [walkmap.vertex :refer [ensure2d ensure3d vertex vertex= vertex?]])) (defn edge "Return an edge between vertices `v1` and `v2`." @@ -36,6 +36,16 @@ #(m/expt (- (% end) (% start)) 2) [:x :y :z]))))) +(defn centre + "Return the vertex that represents the centre of this `edge`." + [edge] + (let [s (ensure3d (:start edge)) + e (ensure3d (:end edge))] + (vertex + (+ (:x s) (/ (- (:x e) (:x s)) 2)) + (+ (:y s) (/ (- (:y e) (:y s)) 2)) + (+ (:z s) (/ (- (:z e) (:z s)) 2))))) + (defn unit-vector "Return an vertex parallel to `e` starting from the coordinate origin. Two edges which are parallel will have the same unit vector." @@ -52,12 +62,10 @@ (defn parallel? "True if all `edges` passed are parallel with one another." - ;; TODO: this bears being wary about, dealing with floating point arithmetic. - ;; Keep an eye out for spurious errors. [& edges] (let [uvs (map unit-vector edges)] (every? - #(= % (first uvs)) + #(vertex= % (first uvs)) (rest uvs)))) (defn collinear? @@ -66,5 +74,116 @@ (parallel? e1 e2 - {:start (:start e1) :end (:start e2)})) + (if (vertex= (:start e1) (:start e2)) + {:start (:start e1) :end (:end e2)} + {:start (:start e1) :end (:start e2)}))) + +(defn collinear2d? + "True if the projections of edges `e1`, `e2` onto the x, y plane are + collinear." + [e1 e2] + (collinear? {:start (ensure2d (:start e1)) :end (ensure2d (:end e1))} + {:start (ensure2d (:start e2)) :end (ensure2d (:end e2))})) + +(defn minimaxd + "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." + [edge coord f] + (apply f (list (coord (:start edge)) (coord (:end edge))))) + +(defn on? + "True if the vertex `v` is on the edge `e`." + [e v] + (let [p (ensure3d (:start e)) + q (ensure3d v) + r (ensure3d (:end e))] + (and + (collinear? (edge p q) (edge q r)) + (<= (:x q) (max (:x p) (:x r))) + (>= (:x q) (min (:x p) (:x r))) + (<= (:y q) (max (:y p) (:y r))) + (>= (:y q) (min (:y p) (:y r))) + (<= (:z q) (max (:z p) (:z r))) + (>= (:z q) (min (:z p) (:z r)))))) + +(defn on2d? + "True if vertex `v` is on edge `e` when projected onto the x, y plane." + [e v] + (on? (edge (ensure2d (:start e)) (ensure2d (:end e))) v)) + +(defn overlaps2d? + "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." + [e1 e2] + (when (and (edge? e1) (edge? e2)) + (and + (> (minimaxd e1 :x max) (minimaxd e2 :x min)) + (< (minimaxd e1 :x min) (minimaxd e2 :x max)) + (> (minimaxd e1 :y max) (minimaxd e2 :y min)) + (< (minimaxd e1 :y min) (minimaxd e2 :y max))))) + +;; Don't think I need this. +;; (defn orientation +;; "Determine whether the ordered sequence of vertices `p`, `q` and `r` run +;; clockwise, collinear or anticlockwise in the x,y plane." +;; [p q r] +;; (let [v (- (* (- (:y q) (:y p)) (- (:x r) (:x q))) +;; (* (- (:x q) (:x p)) (- (:y r) (:y q))))] +;; (cond +;; (zero? v) :collinear +;; (pos? v) :clockwise +;; :else +;; :anticlockwise))) + +(defn intersection2d + "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." + [e1 e2] + (if (and (edge? e1) (edge? e2)) + (when + (overlaps2d? e1 e2) ;; relatively cheap check + (if + (collinear2d? e1 e2) + ;; any point within the overlap will do, but we'll pick the end of e1 + ;; which is on e2 + (if (on2d? e2 (:start e1)) (:start e1) (:end e1)) + ;; blatantly stolen from + ;; https://gist.github.com/cassiel/3e725b49670356a9b936 + (let [x1 (:x (:start e1)) + x2 (:x (:end e1)) + x3 (:x (:start e2)) + x4 (:x (:end e2)) + y1 (:y (:start e1)) + y2 (:y (:end e1)) + y3 (:y (:start e2)) + y4 (:y (:end e2)) + denom (- (* (- x1 x2) (- y3 y4)) + (* (- y1 y2) (- x3 x4))) + x1y2-y1x2 (- (* x1 y2) (* y1 x2)) + x3y4-y3x4 (- (* x3 y4) (* y3 x4)) + px-num (- (* x1y2-y1x2 (- x3 x4)) + (* (- x1 x2) x3y4-y3x4)) + py-num (- (* x1y2-y1x2 (- y3 y4)) + (* (- y1 y2) x3y4-y3x4)) + result (when-not (zero? denom) + (vertex (/ px-num denom) (/ py-num denom)))] + (when (and result (on2d? e1 result) (on2d? e2 result)) result)))) + (throw (IllegalArgumentException. + (str + "Both `e1` and `e2` must be edges." + (map #(or (:kind %) (type %)) [e1 e2])))))) diff --git a/src/walkmap/geometry.clj b/src/walkmap/geometry.clj index f152298..d1a4fbe 100644 --- a/src/walkmap/geometry.clj +++ b/src/walkmap/geometry.clj @@ -1,24 +1,17 @@ (ns walkmap.geometry (:require [clojure.math.combinatorics :as combo] - [clojure.math.numeric-tower :as m] - [walkmap.edge :as e] - [walkmap.path :refer [path? polygon->path]] - [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :as v])) - -(defn on? - "True if the vertex `v` is on the edge `e`." - [e v] - (let [p (v/ensure3d (:start e)) - q (v/ensure3d v) - r (v/ensure3d (:end e))] - (and - (e/collinear? p q r) - (<= (:x q) (max (:x p) (:x r))) - (>= (:x q) (min (:x p) (:x r))) - (<= (:y q) (max (:y p) (:y r))) - (>= (:y q) (min (:y p) (:y r))) - (<= (:z q) (max (:z p) (:z r))) - (>= (:z q) (min (:z p) (:z r)))))) - + [clojure.math.numeric-tower :as m])) +(defn =ish + "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to + within `tolerance` (defaults to one part in a million)." + ([n1 n2] + (if (and (number? n1) (number? n2)) + (let [m (m/abs (min n1 n2)) + t (if (zero? m) 0.000001 (* 0.000001 m))] + (=ish n1 n2 t)) + (= n1 n2))) + ([n1 n2 tolerance] + (if (and (number? n1) (number? n2)) + (< (m/abs (- n1 n2)) tolerance) + (= n1 n2)))) diff --git a/src/walkmap/path.clj b/src/walkmap/path.clj index ebd47c3..5ea5a5e 100644 --- a/src/walkmap/path.clj +++ b/src/walkmap/path.clj @@ -8,11 +8,11 @@ (defn path? "True if `o` satisfies the conditions for a path. A path shall be a map - having the key `:nodes`, whose value shall be a sequence of vertices as + having the key `:vertices`, whose value shall be a sequence of vertices as defined in `walkmap.vertex`." [o] (let - [v (:nodes o)] + [v (:vertices o)] (and (seq? v) (> (count v) 2) @@ -25,7 +25,7 @@ [& vertices] (if (every? vertex? vertices) - {:nodes vertices :id (keyword (gensym "path")) :kind :path} + {:vertices vertices :id (keyword (gensym "path")) :kind :path} (throw (IllegalArgumentException. "Each item on path must be a vertex.")))) (defn polygon->path @@ -38,7 +38,7 @@ [o] (if (polygon? o) - (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) + (assoc (dissoc o :vertices) :kind :path :vertices (concat (:vertices o) (list (first (:vertices o))))) (throw (IllegalArgumentException. "Not a polygon!")))) (defn path->edges @@ -60,7 +60,7 @@ (e/edge (first o) (rest o)) (path->edges (rest o)))) (path? o) - (path->edges (:nodes o)) + (path->edges (:vertices o)) :else (throw (IllegalArgumentException. "Not a path or sequence of vertices!")))) diff --git a/src/walkmap/polygon.clj b/src/walkmap/polygon.clj index beaa78d..dc92c7e 100644 --- a/src/walkmap/polygon.clj +++ b/src/walkmap/polygon.clj @@ -13,6 +13,7 @@ (coll? v) (> (count v) 2) (every? vertex? v) + (:id o) (or (nil? (:kind o)) (= (:kind o) :polygon))))) diff --git a/src/walkmap/stl.clj b/src/walkmap/stl.clj index 123bb49..89e6c9e 100644 --- a/src/walkmap/stl.clj +++ b/src/walkmap/stl.clj @@ -5,7 +5,9 @@ [me.raynes.fs :as fs] [org.clojars.smee.binary.core :as b] [taoensso.timbre :as l :refer [info error spy]] + [walkmap.edge :as e] [walkmap.polygon :refer [polygon?]] + [walkmap.tag :refer [tag]] [walkmap.vertex :as v]) (:import org.clojars.smee.binary.core.BinaryIO java.io.DataInput)) @@ -51,35 +53,76 @@ :count :uint-le :facets (b/repeated facet))) +(defn centre + "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." + [facet] + (let [vs (:vertices facet) + v1 (first vs) + opposite (e/edge (nth vs 1) (nth vs 2)) + oc (e/centre opposite)] + (assoc + facet + :centre + (v/vertex + (+ (:x v1) (* (- (:x oc) (:x v1)) 2/3)) + (+ (:y v1) (* (- (:y oc) (:y v1)) 2/3)) + (+ (:z v1) (* (- (:z oc) (:z v1)) 2/3)))))) + (defn canonicalise - "Objects read in from STL won't have all the keys/values we need them to have." - [o] - (cond - (and (coll? o) (not (map? o))) (map canonicalise o) - ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?` - (:facets o) (assoc o - :kind :stl - :id (or (:id o) (keyword (gensym "stl"))) - :facets (canonicalise (:facets o))) - ;; if it has :vertices it's a polygon, but it doesn't yet conform to `polygon?` - (:vertices o) (assoc o - :id (or (:id o) (keyword (gensym "poly"))) - :kind :polygon - :vertices (canonicalise (:vertices o))) - ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?` - (:x o) (v/canonicalise o) - ;; shouldn't happen - :else o)) + "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." + ([o] (canonicalise o :height)) + ([o map-kind] + (when-not + (keyword? map-kind) + (throw (IllegalArgumentException. + (subs (str "Must be a keyword: " (or map-kind "nil")) 0 80)))) + (cond + (and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o) + ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?` + (:facets o) (assoc o + :kind :stl + :id (or (:id o) (keyword (gensym "stl"))) + :facets (canonicalise (:facets o) map-kind)) + ;; if it has :vertices it's a polygon, but it doesn't yet conform to `polygon?` + (:vertices o) (centre + (tag + (assoc o + :id (or (:id o) (keyword (gensym "poly"))) + :kind :polygon + :vertices (canonicalise (:vertices o) map-kind)) + :facet map-kind)) + ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?` + (:x o) (v/canonicalise o) + ;; shouldn't happen + :else o))) (defn decode-binary-stl "Parse a binary STL file from this `filename` and return an STL structure - representing its contents. + 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." - [filename] - (let [in (io/input-stream filename)] - (canonicalise (b/decode binary-stl in)))) + ([filename] + (decode-binary-stl filename :height)) + ([filename map-kind] + (when-not + (keyword? map-kind) + (throw (IllegalArgumentException. + (subs (str "Must be a keyword: " (or map-kind "nil")) 0 80)))) + (let [in (io/input-stream filename)] + (canonicalise (b/decode binary-stl in) map-kind)))) (defn- vect->str [prefix v] (str prefix " " (:x v) " " (:y v) " " (:z v) "\n")) diff --git a/src/walkmap/superstructure.clj b/src/walkmap/superstructure.clj index b41c459..abd0375 100644 --- a/src/walkmap/superstructure.clj +++ b/src/walkmap/superstructure.clj @@ -6,6 +6,22 @@ [walkmap.utils :as u] [walkmap.vertex :as v])) +;; TODO: Think about reification/dereification. How can we cull a polygon, if +;; some vertices still index it? I *think* that what's needed is that when +;; we store something in the superstructure, we replace all its vertices (and +;; other dependent structures, if any with their ids - as well as, obviously, +;; adding/merging those vertices/dependent structures into the superstructure +;; as first class objects in themselves. That means, for each identified thing, +;; the superstructure only contains one copy of it. +;; +;; The question then is, when we want to do things with those objects, do we +;; exteract a copy with its dependent structures fixed back up (reification), +;; or do we indirect through the superstructure every time we want to access +;; them? In a sense, the copy in the superstructure is the 'one true copy', +;; but it may become very difficult then to have one true copy of the +;; superstructure - unless we replace the superstructure altogether with a +;; database, which may be the Right Thing To Do. + (defn index-vertex "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 @@ -14,11 +30,6 @@ 2. `o` is not a map; 3. `o` does not have a value for the key `:id`; 4. `v` is not a vertex." - ;; two copies of the same vertex are not identical enough to one another - ;; to be used as keys in a map. So our vertices need to have ids, and we need - ;; to key the vertex-index by vertex ids. - ;; TODO: BUT WE CANNOT USE GENSYMED ids, because two vertices with the same - ;; vertices must have the same id! [s o v] (if-not (v/vertex? o) (if (:id o) diff --git a/src/walkmap/tag.clj b/src/walkmap/tag.clj index 5502fff..72b4977 100644 --- a/src/walkmap/tag.clj +++ b/src/walkmap/tag.clj @@ -26,7 +26,7 @@ (defn tag "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 + if they are not already present. It is an error (and an exception will be thrown) if 1. `object` is not a map; diff --git a/src/walkmap/utils.clj b/src/walkmap/utils.clj index 60b0977..95f26f6 100644 --- a/src/walkmap/utils.clj +++ b/src/walkmap/utils.clj @@ -1,6 +1,7 @@ (ns walkmap.utils "Miscellaneous utility functions." - (:require [walkmap.path :as p] + (:require [clojure.math.numeric-tower :as m] + [walkmap.path :as p] [walkmap.polygon :as q] [walkmap.vertex :as v])) @@ -14,13 +15,9 @@ (defn vertices "If `o` is an object with vertices, return those vertices, else nil." - ;; TODO: it's possibly a design mistake that I'm currently distinguishing - ;; between polygons and paths on the basis that one has `:vertices` and - ;; the other has `:nodes`. Possibly it would be better to have a key - ;; `:closed` which was `true` for polygons, `false` (or missing) for - ;; paths. [o] (cond (v/vertex? o) (list o) (q/polygon? o) (:vertices o) - (p/path? o) (:nodes o))) + (p/path? o) (:vertices o))) + diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index ea8d8bc..6c92f11 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -2,17 +2,32 @@ "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`.") + two vertices, create an edge from them and use `walkmap.edge/length`." + (:require [clojure.math.numeric-tower :as m] + [clojure.string :as s] + [walkmap.geometry :refer [=ish]])) (defn vertex-key "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`." + `: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." [o] - (cond - (and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}")) - (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}")) - :else (throw (IllegalArgumentException. "Not a vertex.")))) + (keyword + (s/replace + (cond + (and (:x o) (:y o) (:z o)) + (str "vert_" (:x o) "_" (:y o) "_" (:z o)) + (and (:x o) (:y o)) + (str "vert_" (:x o) "_" (:y o)) + :else + (throw (IllegalArgumentException. + (subs (str "Not a vertex: " (or o "nil")) 0 80)))) + "." + "-"))) (defn vertex? "True if `o` satisfies the conditions for a vertex. That is, essentially, @@ -32,6 +47,13 @@ (or (nil? (:z o)) (number? (:z o))) (or (nil? (:kind o)) (= (:kind o) :vertex)))) +(defn vertex= + "True if vertices `v1`, `v2` represent the same vertex." + [v1 v2] + (every? + #(=ish (% v1) (% v2)) + [:x :y :z])) + (defn vertex "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`. @@ -41,7 +63,8 @@ (let [v {:x x :y y :kind :vertex}] (assoc v :id (vertex-key v)))) ([x y z] - (assoc (vertex x y) :z z))) + (let [v (assoc (vertex x y) :z z)] + (assoc v :id (vertex-key v))))) (defn canonicalise "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, @@ -54,7 +77,12 @@ (number? (:y o)) (or (nil? (:z o)) (number? (:z o)))) (assoc o :kind :vertex :id (vertex-key o)) - (throw (IllegalArgumentException. "Not a proto-vertex: must have numeric `:x` and `:y`.")))) + (throw + (IllegalArgumentException. + (subs + (str "Not a proto-vertex: must have numeric `:x` and `:y`: " + (or o "nil")) + 0 80))))) (def ensure3d "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise @@ -68,7 +96,9 @@ (ensure3d o 0.0)) ([o dflt] (cond - (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!")) + (not (vertex? o)) (throw + (IllegalArgumentException. + (subs (str "Not a vertex: " (or o "nil")) 0 80))) (:z o) o :else (assoc o :z dflt)))))) @@ -79,4 +109,6 @@ (if (vertex? o) (assoc o :z 0.0) - (throw (IllegalArgumentException. "Not a vertex!")))))) + (throw + (IllegalArgumentException. + (subs (str "Not a vertex: " (or o "nil")) 0 80))))))) diff --git a/test/walkmap/edge_test.clj b/test/walkmap/edge_test.clj index e8dc303..22b9222 100644 --- a/test/walkmap/edge_test.clj +++ b/test/walkmap/edge_test.clj @@ -1,5 +1,6 @@ (ns walkmap.edge-test - (:require [clojure.test :refer :all] + (:require [clojure.math.numeric-tower :as m] + [clojure.test :refer :all] [walkmap.edge :refer :all] [walkmap.vertex :refer [vertex]])) @@ -19,6 +20,32 @@ :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Value of x in start is not a number") (is (false? (edge? "I am not an edge")) "Edge mustbe a map."))) +(deftest collinear-test + (testing "collinearity" + (is (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3.0 :y 4.0 :z 0.0 :id 'bar}} + {:start {:x 3.0 :y 4.0 :z 0.0 :id 'foo} :end {:x 9.0 :y 12.0 :z 0.0 :id 'bar}}) + "Should be") + (is (not + (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3 :y 4 :z 0.0 :id 'bar}} + {:start {:x 1.0 :y 2.0 :z 3.5 :id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :id 'bar}})) + "Should not be!") + (is (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3.0 :y 4.0 :z 0.0 :id 'bar}} + {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 9.0 :y 12.0 :z 0.0 :id 'bar}}) + "Edge case: same start location") + (is (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 9.0 :y 12.0 :z 0.0 :id 'bar}} + {:start {:x 3.0 :y 4.0 :z 0.0 :id 'foo} :end {:x 9.0 :y 12.0 :z 0.0 :id 'bar}}) + "Edge case: same end location") + )) + +(deftest collinear2d-test + (testing "Collinearity when projected onto the x,y plane." + (is (collinear2d? (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + (edge (vertex 4.0 4.0) (vertex 6.0 6.0))) + "Collinear, overlapping.") + (is (collinear2d? (edge (vertex 1.0 1.0 0.0) (vertex 5.0 5.0 5.0)) + (edge (vertex 4.0 4.0 79.3) (vertex 6.0 6.0 0.2))) + "Separated in the z axis, but collinear in x, y."))) + (deftest construction-test (testing "Construction of edges." (is (edge? (edge (vertex 1.0 2.0 3.0) (vertex 4.0 8.0 12.0))) @@ -28,18 +55,46 @@ (is (thrown? IllegalArgumentException (edge (vertex 1 2) "Not a vertex")) "If second argument is not a vertex, we should get an exception."))) +(deftest intersection2d-test + (testing "intersection of two edges projected onto the x,y plane." + (is (thrown? IllegalArgumentException + (intersection2d + (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + "This is not an edge")) + "Not an edge (second arg) -> exception.") + (is (thrown? IllegalArgumentException + (intersection2d + "This is not an edge" + (edge (vertex 1.0 1.0) (vertex 5.0 5.0)))) + "Not an edge (first arg) -> exception.") + (is (nil? (intersection2d (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + (edge (vertex 1.0 2.0) (vertex 5.0 6.0)))) + "Parallel but not intersecting.") + (is (:x (intersection2d (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + (edge (vertex 4.0 4.0) (vertex 6.0 6.0))) + 5.0) + "Collinear, overlapping, should choose the overlapping end of the first edge.") + (is (= (:x (intersection2d (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + (edge (vertex 1.0 5.0) (vertex 5.0 1.0)))) + 3.0) + "Crossing, should intersect at 3.0, 3.0: x coord.") + (is (= (:y (intersection2d (edge (vertex 1.0 1.0) (vertex 5.0 5.0)) + (edge (vertex 1.0 5.0) (vertex 5.0 1.0)))) + 3.0) + "Crossing, should intersect at 3.0, 3.0: y coord.") + (is (= (:y (intersection2d (edge (vertex 1.0 1.0 0.0) (vertex 5.0 5.0 0.0)) + (edge (vertex 1.0 5.0 999) (vertex 5.0 1.0 379)))) + 3.0) + "Crossing, presence of z coordinate should make no difference"))) + (deftest length-test (testing "length of an edge" (is (= (length {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3.0 :y 4.0 :z 0.0 :id 'bar}}) 5.0)))) -(deftest unit-vector-test - (testing "deriving the unit vector" - (is (= - (unit-vector {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3 :y 4 :z 0.0 :id 'bar}}) - {:x 0.6, :y 0.8, :z 0.0})) - (is (= - (unit-vector {:start {:x 1.0 :y 2.0 :z 3.5 :id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :id 'bar}}) - {:x 0.6, :y 0.8, :z 0.0})))) +(deftest minimad-test + (testing "finding minimum and maximum coordinates of edges." + (is (= (minimaxd (edge (vertex 1.0 2.0 3.0) (vertex 4.0 8.0 12.0)) :x min) 1.0)) + (is (= (minimaxd (edge (vertex 1.0 2.0 3.0) (vertex 4.0 8.0 12.0)) :y max) 8.0)))) (deftest parallel-test (testing "parallelism" @@ -51,12 +106,16 @@ {:start {:x 1.0 :y 2.0 :z 3.5 :id 'foo} :end {:x 4.0 :y 6.0 :z 3.49 :id 'bar}})) "Should not be!"))) -(deftest collinear-test - (testing "collinearity" - (is (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3.0 :y 4.0 :z 0.0 :id 'bar}} - {:start {:x 3.0 :y 4.0 :z 0.0 :id 'foo} :end {:x 9.0 :y 12.0 :z 0.0 :id 'bar}}) - "Should be") - (is (not - (collinear? {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3 :y 4 :z 0.0 :id 'bar}} - {:start {:x 1.0 :y 2.0 :z 3.5 :id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :id 'bar}})) - "Should not be!"))) +(deftest overlaps2d-test + (testing "whether two edges are in the same area of the x,y plane." + (is (false? (overlaps2d? (edge (vertex 1 1) (vertex 4 4)) (edge (vertex 5 5) (vertex 8 8))))) + (is (overlaps2d? (edge (vertex 1 1) (vertex 4 4)) (edge (vertex 4 4) (vertex 1 1)))))) + +(deftest unit-vector-test + (testing "deriving the unit vector" + (is (= + (unit-vector {:start {:x 0.0 :y 0.0 :z 0.0 :id 'foo} :end {:x 3 :y 4 :z 0.0 :id 'bar}}) + {:x 0.6, :y 0.8, :z 0.0})) + (is (= + (unit-vector {:start {:x 1.0 :y 2.0 :z 3.5 :id 'foo} :end {:x 4.0 :y 6.0 :z 3.5 :id 'bar}}) + {:x 0.6, :y 0.8, :z 0.0})))) diff --git a/test/walkmap/geometry_test.clj b/test/walkmap/geometry_test.clj new file mode 100644 index 0000000..eb946fb --- /dev/null +++ b/test/walkmap/geometry_test.clj @@ -0,0 +1,14 @@ +(ns walkmap.geometry-test + (:require [clojure.test :refer :all] + [walkmap.geometry :refer :all])) + +(deftest =ish-tests + (testing "Rough equality" + (is (=ish 5.00000001 5.00000002) "Close enough.") + (is (=ish 5 5) "Perfect.") + (is (not (=ish 5.01 5.02)) "Not close enough.") + (is (=ish 22/7 3.142857) "We hope so!") + (is (=ish 0 0.0) "Tricky conrer case!") + (is (=ish :foo :foo) "Fails over to plain old equals for non-numbers.") + (is (=ish 6 5 10000) "If tolerance is wide enough, anything can be equal.") + (is (=ish "hello" "goodbye" 10000) "Well, except non-numbers, of course.")))