From f616992191e17b4e93498421ed1681dac575184d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 25 May 2020 23:55:52 +0100 Subject: [PATCH 1/5] #3: Massive changes, superstructure now kind-of works More unit tests now, and they all pass. More work needed, but this is very promising. --- docs/cloverage/index.html | 189 +++++--- docs/cloverage/walkmap/core.clj.html | 113 +---- docs/cloverage/walkmap/ocean.clj.html | 80 ++++ docs/cloverage/walkmap/path.clj.html | 90 ++-- docs/cloverage/walkmap/polygon.clj.html | 23 +- docs/cloverage/walkmap/stl.clj.html | 422 ++++++++++-------- .../cloverage/walkmap/superstructure.clj.html | 236 ++++++++++ docs/cloverage/walkmap/svg.clj.html | 348 +++++++++++---- docs/cloverage/walkmap/utils.clj.html | 86 ++++ docs/cloverage/walkmap/vertex.clj.html | 249 +++++++---- docs/codox/dali-performance.html | 171 +++++++ docs/codox/index.html | 2 +- docs/codox/intro.html | 17 +- docs/codox/walkmap.core.html | 3 +- docs/codox/walkmap.edge.html | 2 +- docs/codox/walkmap.geometry.html | 2 +- docs/codox/walkmap.ocean.html | 4 + docs/codox/walkmap.path.html | 4 +- docs/codox/walkmap.polygon.html | 2 +- docs/codox/walkmap.stl.html | 6 +- docs/codox/walkmap.superstructure.html | 19 + docs/codox/walkmap.svg.html | 2 +- docs/codox/walkmap.utils.html | 3 + docs/codox/walkmap.vertex.html | 6 +- src/walkmap/BinaryToASCII.py | 52 --- src/walkmap/path.clj | 12 +- src/walkmap/polygon.clj | 5 +- src/walkmap/stl.clj | 26 +- src/walkmap/superstructure.clj | 76 ++++ src/walkmap/utils.clj | 26 ++ src/walkmap/vertex.clj | 25 +- test/walkmap/edge_test.clj | 43 +- test/walkmap/stl_test.clj | 96 ++++ 33 files changed, 1768 insertions(+), 672 deletions(-) create mode 100644 docs/cloverage/walkmap/ocean.clj.html create mode 100644 docs/cloverage/walkmap/superstructure.clj.html create mode 100644 docs/cloverage/walkmap/utils.clj.html create mode 100644 docs/codox/dali-performance.html create mode 100644 docs/codox/walkmap.ocean.html create mode 100644 docs/codox/walkmap.superstructure.html create mode 100644 docs/codox/walkmap.utils.html delete mode 100644 src/walkmap/BinaryToASCII.py create mode 100644 src/walkmap/superstructure.clj create mode 100644 src/walkmap/utils.clj create mode 100644 test/walkmap/stl_test.clj diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index adaca25..f066158 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -16,18 +16,14 @@ walkmap.core
6
43
-12.24 % + style="width:100.0%; + float:left;"> 1 +100.00 %
5
8
-38.46 % -44613 + style="width:100.0%; + float:left;"> 1 +100.00 % +911 walkmap.edge
24313 - walkmap.path
3
51
-5.56 % + walkmap.ocean
5
23
+17.86 %
3
10
-23.08 % -30413 + style="width:50.0%; + float:left;"> 4
4
+50.00 % +2448 + + + walkmap.path
4
91
+4.21 % +
4
15
+21.05 % +38519 walkmap.polygon
2
25
-7.41 % + style="width:91.30434782608695%; + float:left;"> 42
4
+91.30 %
2
6
-25.00 % -1738 + style="width:88.88888888888889%; + float:left;"> 8
1
+100.00 % +1839 walkmap.stl
46
221
-17.23 % + style="width:55.989583333333336%; + float:left;"> 215
169
+55.99 %
18
46
-28.13 % -1261264 + style="width:38.1578947368421%; + float:left;"> 29
10
37
+51.32 % +1481376 + + + walkmap.superstructure
119
47
+71.69 % +
20
4
5
+82.76 % +76829 walkmap.svg
4
135
-2.88 % + style="width:3.7542662116040955%; + float:left;"> 11
282
+3.75 %
3
32
-8.57 % -50235 + style="width:12.121212121212121%; + float:left;"> 8
58
+12.12 % +108766 + + + walkmap.utils
24
14
+63.16 % +
6
2
2
+80.00 % +26210 walkmap.vertex
62
23
-72.94 % + style="width:83.91959798994975%; + float:left;"> 167
32
+83.92 %
14
3
5
-77.27 % -43522 + style="width:60.60606060606061%; + float:left;"> 20
7
6
+81.82 % +66733 Totals: -27.77 % +46.27 % -39.45 % +51.59 % diff --git a/docs/cloverage/walkmap/core.clj.html b/docs/cloverage/walkmap/core.clj.html index 58adc64..691bd4d 100644 --- a/docs/cloverage/walkmap/core.clj.html +++ b/docs/cloverage/walkmap/core.clj.html @@ -8,10 +8,10 @@ 001  (ns walkmap.core
- 002    "At this stage, primarily utility functions dealing with stereolithography + 002    "This namespace mostly gets used as a scratchpad for ideas which haven't yet
- 003    (STL) files. Not a stable API yet!" + 003    solidified."
004    (:require [clojure.java.io :as io :refer [file output-stream input-stream]] @@ -26,115 +26,10 @@ 007              [me.raynes.fs :as fs]
- 008              [taoensso.timbre :as l :refer [info error spy]] -
- - 009              [walkmap.stl :refer [decode-binary-stl]] -
- - 010              [walkmap.svg :refer [stl->svg]])) + 008              [taoensso.timbre :as l :refer [info error spy]]))
- 011   -
- - 012  (def ^:dynamic *sea-level* -
- - 013    "The sea level on heightmaps we're currently handling. If characters are to -
- - 014    be able to swin in the sea, we must model the sea bottom, so we need -
- - 015    heightmaps which cover at least the continental shelf. However, the sea -
- - 016    bottom is not walkable territory and can be culled from walkmaps. -
- - 017   -
- - 018    **Note** must be a floating point number. `(= 0 0.0)` returns `false`!" -
- - 019    0.0) -
- - 020   -
- - 021  (defn ocean? -
- - 022    "Of a `facet`, is the altitude of every vertice equal to `*sea-level*`?" -
- - 023    [facet] -
- - 024    (every? -
- - 025      #(= % *sea-level*) -
- - 026      (map :z (:vertices facet)))) -
- - 027   -
- - 028  (defn cull-ocean-facets -
- - 029    "Ye cannae walk on water. Remove all facets from this `stl` structure which -
- - 030    are at sea level." -
- - 031    [stl] -
- - 032    (assoc stl :facets (remove ocean? (:facets stl)))) -
- - 033   -
- - 034  (defn binary-stl-file->svg -
- - 035    "Given only an `in-filename`, parse the indicated file, expected to be -
- - 036    binary STL, and return an equivalent SVG structure. Given both `in-filename` -
- - 037    and `out-filename`, as side-effect write the SVG to the indicated output file." -
- - 038    ([in-filename] -
- - 039     (stl->svg (cull-ocean-facets (decode-binary-stl in-filename)))) -
- - 040    ([in-filename out-filename] -
- - 041     (let [s (binary-stl-file->svg in-filename)] -
- - 042       (spit out-filename (html s)) -
- - 043       s))) -
- - 044   + 009  
diff --git a/docs/cloverage/walkmap/ocean.clj.html b/docs/cloverage/walkmap/ocean.clj.html new file mode 100644 index 0000000..eb20c01 --- /dev/null +++ b/docs/cloverage/walkmap/ocean.clj.html @@ -0,0 +1,80 @@ + + + + walkmap/ocean.clj + + + + 001  (ns walkmap.ocean +
+ + 002    "Deal with (specifically, at this stage, cull) ocean areas") +
+ + 003   +
+ + 004  (def ^:dynamic *sea-level* +
+ + 005    "The sea level on heightmaps we're currently handling. If characters are to +
+ + 006    be able to swin in the sea, we must model the sea bottom, so we need +
+ + 007    heightmaps which cover at least the continental shelf. However, the sea +
+ + 008    bottom is not walkable territory and can be culled from walkmaps. +
+ + 009   +
+ + 010    **Note** must be a floating point number. `(= 0 0.0)` returns `false`!" +
+ + 011    0.0) +
+ + 012   +
+ + 013  (defn ocean? +
+ + 014    "Of a `facet`, is the altitude of every vertice equal to `*sea-level*`?" +
+ + 015    [facet] +
+ + 016    (every? +
+ + 017      #(= % *sea-level*) +
+ + 018      (map :z (:vertices facet)))) +
+ + 019   +
+ + 020  (defn cull-ocean-facets +
+ + 021    "Ye cannae walk on water. Remove all facets from this `stl` structure which +
+ + 022    are at sea level." +
+ + 023    [stl] +
+ + 024    (assoc stl :facets (remove ocean? (:facets stl)))) +
+ + diff --git a/docs/cloverage/walkmap/path.clj.html b/docs/cloverage/walkmap/path.clj.html index 6b58a3d..7e8c4d2 100644 --- a/docs/cloverage/walkmap/path.clj.html +++ b/docs/cloverage/walkmap/path.clj.html @@ -40,7 +40,7 @@ 012      [v (:nodes o)]
- + 013      (and
@@ -49,50 +49,74 @@ 015        (> (count v) 2)
- - 016        (every? vertex? v)))) + + 016        (every? vertex? v) +
+ + 017        (or (nil? (:kind o)) (= (:kind o) :path)))))
- 017   + 018  
- 018  (defn polygon->path + 019  (defn make-path
- 019    "If `o` is a polygon, return an equivalent path. What's different about -
- - 020    a path is that in polygons there is an implicit edge between the first -
- - 021    vertex and the last. In paths, there isn't, so we need to add that -
- - 022    edge explicitly. -
- - 023   -
- - 024    If `o` is not a polygon, will throw an exception." -
- - 025    [o] + 020    [nodes]
- 026    (if + 021    (if +
+ + 022      (every? vertex? nodes) +
+ + 023      {:nodes nodes :id (keyword (gensym "path")) :kind :path}
- 027      (polygon? o) -
- - 028      (assoc (dissoc o :vertices) :nodes (concat (:vertices o) (list (first (:vertices o))))) -
- - 029      (throw (Exception. "Not a polygon!")))) + 024      (throw (Exception. "Each item on path must be a vertex."))))
- 030   + 025   +
+ + 026  (defn polygon->path +
+ + 027    "If `o` is a polygon, return an equivalent path. What's different about +
+ + 028    a path is that in polygons there is an implicit edge between the first +
+ + 029    vertex and the last. In paths, there isn't, so we need to add that +
+ + 030    edge explicitly. +
+ + 031   +
+ + 032    If `o` is not a polygon, will throw an exception." +
+ + 033    [o] +
+ + 034    (if +
+ + 035      (polygon? o) +
+ + 036      (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) +
+ + 037      (throw (Exception. "Not a polygon!")))) +
+ + 038  
diff --git a/docs/cloverage/walkmap/polygon.clj.html b/docs/cloverage/walkmap/polygon.clj.html index f8ebc8b..0be1cbd 100644 --- a/docs/cloverage/walkmap/polygon.clj.html +++ b/docs/cloverage/walkmap/polygon.clj.html @@ -31,29 +31,32 @@ 009    [o]
- + 010    (let
- + 011      [v (:vertices o)]
- + 012      (and
- - 013        (seq? v) + + 013        (coll? v)
- + 014        (> (count v) 2)
- - 015        (every? vertex? v)))) + + 015        (every? vertex? v)
- - 016   + + 016        (or (nil? (:kind o)) (= (:kind o) :polygon)))))
017  
+ + 018   +
diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html index 8e48e58..2c577c0 100644 --- a/docs/cloverage/walkmap/stl.clj.html +++ b/docs/cloverage/walkmap/stl.clj.html @@ -26,361 +26,427 @@ 007              [taoensso.timbre :as l :refer [info error spy]]

- 008              [walkmap.polygon :refer [polygon?]]) + 008              [walkmap.polygon :refer [polygon?]]
- 009    (:import org.clojars.smee.binary.core.BinaryIO + 009              [walkmap.vertex :refer [vertex-key]])
- 010             java.io.DataInput)) + 010    (:import org.clojars.smee.binary.core.BinaryIO +
+ + 011             java.io.DataInput))
- 011   + 012  
- 012  (defn stl? + 013  (defn stl?
- 013    "True if `o` is recogniseable as an STL structure. An STL structure must + 014    "True if `o` is recogniseable as an STL structure. An STL structure must
- 014    have a key `:facets`, whose value must be a sequence of polygons; and + 015    have a key `:facets`, whose value must be a sequence of polygons; and
- 015    may have a key `:header` whose value should be a string, and/or a key + 016    may have a key `:header` whose value should be a string, and/or a key
- 016    `:count`, whose value should be a positive integer. + 017    `:count`, whose value should be a positive integer.
- 017   + 018  
- 018    If `verify-count?` is passed and is not `false`, verify that the value of + 019    If `verify-count?` is passed and is not `false`, verify that the value of
- 019    the `:count` header is equal to the number of facets." + 020    the `:count` header is equal to the number of facets."
- 020    ([o] -
- - 021     (stl? o false)) -
- - 022    ([o verify-count?] -
- - 023     (and -
- - 024       (map? o) -
- - 025       (:facets o) -
- - 026       (every? polygon? (:facets o)) -
- - 027       (if (:header o) (string? (:header o)) true) -
- - 028       (if (:count o) (integer? (:count o)) true) -
- - 029       (if verify-count? (= (:count o) (count (:facets o))) true)))) -
- - 030   -
- - 031  (def vect -
- - 032    "A codec for vectors within a binary STL file." -
- - 033    (b/ordered-map -
- - 034      :x :float-le -
- - 035      :y :float-le -
- - 036      :z :float-le)) -
- - 037   -
- - 038  (def facet -
- - 039    "A codec for a facet (triangle) within a binary STL file." -
- - 040    (b/ordered-map -
- - 041      :normal vect + 021    ([o]
- 042      :vertices [vect vect vect] + 022     (stl? o false))
- 043      :abc :ushort-le)) + 023    ([o verify-count?]
- - 044   -
- - 045  (def binary-stl -
- - 046    "A codec for binary STL files" -
- - 047    (b/ordered-map -
- - 048     :header (b/string "ISO-8859-1" :length 80) ;; for the time being we neither know nor care what's in this. -
- - 049     :count :uint-le + + 024     (and
- 050     :facets (b/repeated facet))) + 025       (map? o) +
+ + 026       (:facets o) +
+ + 027       (every? polygon? (:facets o)) +
+ + 028       (if (:header o) (string? (:header o)) true) +
+ + 029       (if (:count o) (integer? (:count o)) true) +
+ + 030       (or (nil? (:kind o)) (= (:kind o) :stl)) +
+ + 031       (if verify-count? (= (:count o) (count (:facets o))) true))))
- 051   + 032  
- 052  (defn decode-binary-stl + 033  (def vect
- 053    "Parse a binary STL file from this `filename` and return an STL structure + 034    "A codec for vectors within a binary STL file." +
+ + 035    (b/ordered-map
- 054    representing its contents. + 036      :x :float-le +
+ + 037      :y :float-le +
+ + 038      :z :float-le))
- 055   + 039   +
+ + 040  (def facet
- 056    **NOTE** that we've no way of verifying that the input file is binary STL + 041    "A codec for a facet (triangle) within a binary STL file." +
+ + 042    (b/ordered-map +
+ + 043      :normal vect +
+ + 044      :vertices [vect vect vect]
- 057    data, if it is not this will run but will return garbage." -
- - 058    [filename] -
- - 059    (let [in (io/input-stream filename)] -
- - 060      (b/decode binary-stl in))) + 045      :abc :ushort-le))
- 061   + 046   +
+ + 047  (def binary-stl +
+ + 048    "A codec for binary STL files" +
+ + 049    (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. +
+ + 051     :count :uint-le +
+ + 052     :facets (b/repeated facet))) +
+ + 053   +
+ + 054  (defn canonicalise +
+ + 055    "Objects read in from STL won't have all the keys/values we need them to have." +
+ + 056    [o] +
+ + 057    (cond +
+ + 058      (and (coll? o) (not (map? o))) (map canonicalise o) +
+ + 059      ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?` +
+ + 060      (:facets o) (assoc o +
+ + 061                 :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?` +
+ + 070      (:x o) (assoc o :kind :vertex :id (or (:id o) (vertex-key o))) +
+ + 071      ;; shouldn't happen +
+ + 072      :else o)) +
+ + 073   +
+ + 074  (defn decode-binary-stl +
+ + 075    "Parse a binary STL file from this `filename` and return an STL structure +
+ + 076    representing its contents. +
+ + 077   +
+ + 078    **NOTE** that we've no way of verifying that the input file is binary STL +
+ + 079    data, if it is not this will run but will return garbage." +
+ + 080    [filename] +
+ + 081    (let [in (io/input-stream filename)] +
+ + 082      (canonicalise (b/decode binary-stl in)))) +
+ + 083  
- 062  (defn- vect->str [prefix v] + 084  (defn- vect->str [prefix v]
- 063    (str prefix " " (:x v) " " (:y v) " " (:z v) "\n")) + 085    (str prefix " " (:x v) " " (:y v) " " (:z v) "\n"))
- 064   + 086  
- 065  (defn- facet2str [tri] + 087  (defn- facet2str [tri]
- 066    (str + 088    (str
- 067      (vect->str "facet normal" (:normal tri)) + 089      (vect->str "facet normal" (:normal tri))
- 068      "outer loop\n" + 090      "outer loop\n"
- 069      (apply str + 091      (apply str
- 070             (map + 092             (map
- 071               #(vect->str "vertex" %) + 093               #(vect->str "vertex" %)
- 072               (:vertices tri))) + 094               (:vertices tri)))
- 073      "endloop\nendfacet\n")) + 095      "endloop\nendfacet\n"))
- 074   + 096  
- 075  (defn stl->ascii + 097  (defn stl->ascii
- 076    "Return as a string an ASCII rendering of the `stl` structure." + 098    "Return as a string an ASCII rendering of the `stl` structure."
- 077    ([stl] + 099    ([stl]
- 078     (stl->ascii stl "unknown")) + 100     (stl->ascii stl "unknown"))
- 079    ([stl solidname] + 101    ([stl solidname]
- 080     (str + 102     (str
- 081       "solid " + 103       "solid "
- 082       solidname + 104       solidname
- 083       (s/trim (:header stl)) + 105       (s/trim (:header stl))
- 084       "\n" + 106       "\n"
- 085       (apply + 107       (apply
- 086         str + 108         str
- 087         (map + 109         (map
- 088           facet2str + 110           facet2str
- 089           (:facets stl))) + 111           (:facets stl)))
- 090       "endsolid " + 112       "endsolid "
- 091       solidname + 113       solidname
- 092       "\n"))) + 114       "\n")))
- 093   + 115  
- 094  (defn write-ascii-stl + 116  (defn write-ascii-stl
- 095    "Write an `stl` structure as read by `decode-binary-stl` to this + 117    "Write an `stl` structure as read by `decode-binary-stl` to this
- 096    `filename` as ASCII encoded STL." + 118    `filename` as ASCII encoded STL."
- 097    ([filename stl] + 119    ([filename stl]
- 098     (let [b (fs/base-name filename true)] + 120     (let [b (fs/base-name filename true)]
- 099       (write-ascii-stl + 121       (write-ascii-stl
- 100         filename stl + 122         filename stl
- 101         (subs b 0 (or (s/index-of b ".") (count b)))))) + 123         (subs b 0 (or (s/index-of b ".") (count b))))))
- 102    ([filename stl solidname] + 124    ([filename stl solidname]
- 103     (l/debug "Solid name is " solidname) + 125     (l/debug "Solid name is " solidname)
- 104     (spit + 126     (spit
- 105       filename + 127       filename
- 106       (stl->ascii stl solidname)))) + 128       (stl->ascii stl solidname))))
- 107   + 129  
- 108  (defn binary-stl-to-ascii + 130  (defn binary-stl-to-ascii
- 109    "Convert the binary STL file indicated by `in-filename`, and write it to + 131    "Convert the binary STL file indicated by `in-filename`, and write it to
- 110    `out-filename`, if specified; otherwise, to a file with the same basename + 132    `out-filename`, if specified; otherwise, to a file with the same basename
- 111    as `in-filename` but the extension `.ascii.stl`." + 133    as `in-filename` but the extension `.ascii.stl`."
- 112    ([in-filename] + 134    ([in-filename]
- 113     (let [[_ ext] (fs/split-ext in-filename)] + 135     (let [[_ ext] (fs/split-ext in-filename)]
- 114       (binary-stl-to-ascii + 136       (binary-stl-to-ascii
- 115         in-filename + 137         in-filename
- 116         (str + 138         (str
- 117           (subs + 139           (subs
- 118             in-filename + 140             in-filename
- 119             0 + 141             0
- 120             (or + 142             (or
- 121               (s/last-index-of in-filename ".") + 143               (s/last-index-of in-filename ".")
- 122               (count in-filename))) + 144               (count in-filename)))
- 123           ".ascii" + 145           ".ascii"
- 124           ext)))) + 146           ext))))
- 125    ([in-filename out-filename] + 147    ([in-filename out-filename]
- 126     (write-ascii-stl out-filename (decode-binary-stl in-filename)))) + 148     (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 new file mode 100644 index 0000000..27266ac --- /dev/null +++ b/docs/cloverage/walkmap/superstructure.clj.html @@ -0,0 +1,236 @@ + + + + walkmap/superstructure.clj + + + + 001  (ns walkmap.superstructure +
+ + 002    "single indexing structure for walkmap objects" +
+ + 003    (:require [walkmap.path :as p] +
+ + 004              [walkmap.polygon :as q] +
+ + 005              [walkmap.stl :as s] +
+ + 006              [walkmap.utils :as u] +
+ + 007              [walkmap.vertex :as v])) +
+ + 008   +
+ + 009  (defn index-vertex +
+ + 010    "Return a superstructure like `s` in which object `o` is indexed by vertex +
+ + 011    `v`. It is an error (and an exception may be thrown) if +
+ + 012   +
+ + 013    1. `s` is not a map; +
+ + 014    2. `o` is not a map; +
+ + 015    3. `o` does not have a value for the key `:id`; +
+ + 016    4. `v` is not a vertex." +
+ + 017    ;; two copies of the same vertex are not identical enough to one another +
+ + 018    ;; to be used as keys in a map. So our vertices need to have ids, and we need +
+ + 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] +
+ + 023    (if-not (v/vertex? o) +
+ + 024      (if (:id o) +
+ + 025        (if (v/vertex? v) +
+ + 026          (let [vi (or (:vertex-index s) {}) +
+ + 027                current (or (vi (:id v)) {})] +
+ + 028            ;; deep-merge doesn't merge sets, only maps; so at this +
+ + 029            ;; stage we need to build a map. +
+ + 030            (assoc vi (:id v) (assoc current (:id o) (:id v)))) +
+ + 031          (throw (Exception. "Not a vertex: " v))) +
+ + 032        (throw (Exception. (subs (str "No `:id` value: " o) 0 80)))) +
+ + 033      ;; 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. +
+ + 035      (:vertex-index s))) +
+ + 036   +
+ + 037  (defn index-vertices +
+ + 038    "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 +
+ + 040   +
+ + 041    1. `s` is not a map; +
+ + 042    2. `o` is not a map; +
+ + 043    3. `o` does not have a value for the key `:id`." +
+ + 044    [s o] +
+ + 045    (assoc +
+ + 046      s +
+ + 047      :vertex-index +
+ + 048      (reduce +
+ + 049        u/deep-merge +
+ + 050        (map +
+ + 051          #(index-vertex s o %) +
+ + 052          (u/vertices o))))) +
+ + 053   +
+ + 054  (defn add-to-superstructure +
+ + 055    "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 +
+ + 057    argument is supplied it will be assumed to represent `o` and a new +
+ + 058    superstructure will be returned. +
+ + 059   +
+ + 060    It is an error (and an exception may be thrown) if +
+ + 061   +
+ + 062    1. `s` is not a map; +
+ + 063    2. `o` is not a map, or a sequence of maps." +
+ + 064    ([o] +
+ + 065     (add-to-superstructure {} o)) +
+ + 066    ([s o] +
+ + 067    (cond +
+ + 068      (map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))] +
+ + 069                 (index-vertices (assoc s (:id o') o') o')) +
+ + 070      (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o)) +
+ + 071      (nil? o) o +
+ + 072      :else +
+ + 073      (throw (Exception. (str "Don't know how to index " (or (type o) "nil"))))))) +
+ + 074   +
+ + 075  (:vertex-index (add-to-superstructure (:facets (s/decode-binary-stl "resources/isle_of_man.stl")))) +
+ + 076  (s/decode-binary-stl "resources/isle_of_man.stl") +
+ + diff --git a/docs/cloverage/walkmap/svg.clj.html b/docs/cloverage/walkmap/svg.clj.html index d465adf..2d0b77e 100644 --- a/docs/cloverage/walkmap/svg.clj.html +++ b/docs/cloverage/walkmap/svg.clj.html @@ -20,139 +20,313 @@ 005    (:require [clojure.string :as s]

- 006              [taoensso.timbre :as l :refer [info error spy]] + 006              [dali.io :as neatly-folded-clock]
- 007              [walkmap.polygon :refer [polygon?]] + 007              [hiccup.core :refer [html]]
- 008              [walkmap.vertex :refer [vertex?]])) + 008              [taoensso.timbre :as l :refer [info error spy]] +
+ + 009              [walkmap.ocean :refer [cull-ocean-facets]] +
+ + 010              [walkmap.polygon :refer [polygon?]] +
+ + 011              [walkmap.stl :refer [decode-binary-stl]] +
+ + 012              [walkmap.vertex :refer [vertex?]]))
- 009   + 013  
- 010  (defn- facet->svg-poly + 014  (def ^:dynamic *preferred-svg-render*
- 011    [facet] + 015    "Mainly for debugging dali; switch SVG renderer to use. Expected values:
- - 012    [:polygon + + 016    `:dali`, `:hiccup`."
- - 013     {:points (s/join " " (map #(str (:x %) "," (:y %)) (:vertices facet)))}]) + + 017    :dali)
- 014   + 018   +
+ + 019  (defn- facet->svg-poly +
+ + 020    [facet] +
+ + 021    [:polygon +
+ + 022     {:points (s/join " " (map #(str (:x %) "," (:y %)) (:vertices facet)))}]) +
+ + 023   +
+ + 024  (defn- dali-facet->svg-poly +
+ + 025    [facet] +
+ + 026    (vec +
+ + 027      (cons +
+ + 028        :polygon +
+ + 029        (map #(vec (list (:x %) (:y %))) (:vertices facet))))) +
+ + 030  
- 015  (defn stl->svg + 031  (defn dali-stl->svg
- 016    "Convert this in-memory `stl` structure, as read by `decode-binary-stl`, into + 032    "Format this `stl` as SVG for the `dali` renderer on a page with these
- 017    an in-memory hiccup representation of SVG structure, and return it." + 033    bounds."
- 018    [stl] -
- - 019    (let [minx (reduce -
- - 020                 min -
- - 021                 (map -
- - 022                   #(reduce min (map :x (:vertices %))) -
- - 023                   (:facets stl))) + 034    [stl minx maxx miny maxy]
- 024          maxx (reduce -
- - 025                 max -
- - 026                 (map -
- - 027                   #(reduce max (map :x (:vertices %))) -
- - 028                   (:facets stl))) -
- - 029          miny (reduce -
- - 030                 min -
- - 031                 (map -
- - 032                   #(reduce min (map :y (:vertices %))) -
- - 033                   (:facets stl))) -
- - 034          maxy (reduce -
- - 035                 max -
- - 036                 (map -
- - 037                   #(reduce max (map :y (:vertices %))) -
- - 038                   (:facets stl)))] -
- - 039      [:svg + 035    [:dali/page
- 040       {:xmlns "http://www.w3.org/2000/svg" + 036     {:xmlns "http://www.w3.org/2000/svg"
- 041        :version "1.2" + 037      :version "1.2"
- 042        :width (- maxx minx) + 038      :width (- maxx minx)
- 043        :height (- maxy miny) + 039      :height (- maxy miny)
- 044        :viewBox (s/join " " (map str [minx miny maxx maxy]))} + 040      :viewBox (s/join " " (map str [minx miny maxx maxy]))}
- 045       (vec + 041     (vec
- 046         (cons + 042       (cons
- 047           :g + 043         :g
- 048           (map + 044         (map
- 049             facet->svg-poly + 045           dali-facet->svg-poly
- 050             (:facets stl))))])) + 046           (:facets stl))))]) +
+ + 047   +
+ + 048  (defn hiccup-stl->svg +
+ + 049    "Format this `stl` as SVG for the `hiccup` renderer on a page with these +
+ + 050    bounds." +
+ + 051    [stl minx maxx miny maxy] +
+ + 052    [:svg +
+ + 053     {:xmlns "http://www.w3.org/2000/svg" +
+ + 054      :version "1.2" +
+ + 055      :width (- maxx minx) +
+ + 056      :height (- maxy miny) +
+ + 057      :viewBox (s/join " " (map str [minx miny maxx maxy]))} +
+ + 058     (vec +
+ + 059       (cons +
+ + 060         :g +
+ + 061         (map +
+ + 062           facet->svg-poly +
+ + 063           (:facets stl))))]) +
+ + 064   +
+ + 065  (defn stl->svg +
+ + 066    "Convert this in-memory `stl` structure, as read by `decode-binary-stl`, into +
+ + 067    an in-memory hiccup representation of SVG structure, and return it." +
+ + 068    [stl] +
+ + 069    (let [minx (reduce +
+ + 070                 min +
+ + 071                 (map +
+ + 072                   #(reduce min (map :x (:vertices %))) +
+ + 073                   (:facets stl))) +
+ + 074          maxx (reduce +
+ + 075                 max +
+ + 076                 (map +
+ + 077                   #(reduce max (map :x (:vertices %))) +
+ + 078                   (:facets stl))) +
+ + 079          miny (reduce +
+ + 080                 min +
+ + 081                 (map +
+ + 082                   #(reduce min (map :y (:vertices %))) +
+ + 083                   (:facets stl))) +
+ + 084          maxy (reduce +
+ + 085                 max +
+ + 086                 (map +
+ + 087                   #(reduce max (map :y (:vertices %))) +
+ + 088                   (:facets stl)))] +
+ + 089      (l/info "Generating SVG for " *preferred-svg-render* " renderer") +
+ + 090      (case *preferred-svg-render* +
+ + 091        :hiccup (hiccup-stl->svg stl minx maxx miny maxy) +
+ + 092        :dali (dali-stl->svg stl minx maxx miny maxy) +
+ + 093        (throw (Exception. "Unexpected renderer value: " *preferred-svg-render*))))) +
+ + 094   +
+ + 095  (defn binary-stl-file->svg +
+ + 096    "Given only an `in-filename`, parse the indicated file, expected to be +
+ + 097    binary STL, and return an equivalent SVG structure. Given both `in-filename` +
+ + 098    and `out-filename`, as side-effect write the SVG to the indicated output file." +
+ + 099    ([in-filename] +
+ + 100     (stl->svg (cull-ocean-facets (decode-binary-stl in-filename)))) +
+ + 101    ([in-filename out-filename] +
+ + 102     (let [s (binary-stl-file->svg in-filename)] +
+ + 103       (l/info "Emitting SVG with " *preferred-svg-render* " renderer") +
+ + 104       (case *preferred-svg-render* +
+ + 105         :dali (neatly-folded-clock/render-svg s out-filename) +
+ + 106         :hiccup (spit out-filename (html s)) +
+ + 107         (throw (Exception. "Unexpected renderer value: " *preferred-svg-render*))) +
+ + 108       s)))
diff --git a/docs/cloverage/walkmap/utils.clj.html b/docs/cloverage/walkmap/utils.clj.html new file mode 100644 index 0000000..f0d7116 --- /dev/null +++ b/docs/cloverage/walkmap/utils.clj.html @@ -0,0 +1,86 @@ + + + + walkmap/utils.clj + + + + 001  (ns walkmap.utils +
+ + 002    "Miscellaneous utility functions." +
+ + 003    (:require [walkmap.path :as p] +
+ + 004              [walkmap.polygon :as q] +
+ + 005              [walkmap.vertex :as v])) +
+ + 006   +
+ + 007  (defn deep-merge +
+ + 008    "Recursively merges maps. If vals are not maps, the last value wins." +
+ + 009    ;; TODO: not my implementation, not sure I entirely trust it. +
+ + 010    [& vals] +
+ + 011    (if (every? map? vals) +
+ + 012      (apply merge-with deep-merge vals) +
+ + 013      (last vals))) +
+ + 014   +
+ + 015  (defn vertices +
+ + 016    "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] +
+ + 023    (cond +
+ + 024      (v/vertex? o) (list o) +
+ + 025      (q/polygon? o) (:vertices o) +
+ + 026      (p/path? o) (:nodes o))) +
+ + diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html index 382bc40..538fa98 100644 --- a/docs/cloverage/walkmap/vertex.clj.html +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -14,124 +14,193 @@ 003  

- 004  (defn vertex? + 004  (defn vertex-key
- 005    "True if `o` satisfies the conditions for a vertex. That is, essentially, + 005    "Making sure we get the same key everytime we key a vertex with the same
- 006    that it must rerpresent a two- or three- dimensional vector. A vertex is + 006    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
- 007    shall be a map having at least the keys `:x` and `:y`, where the value of + 007    `:z`."
- 008    those keys is a number. If the key `:z` is also present, its value must also -
- - 009    be a number. -
- - 010   -
- - 011    The name  `vector?` was not used as that would clash with a function of that -
- - 012    name in `clojure.core` whose semantics are entirely different." -
- - 013    [o] -
- - 014    (and -
- - 015      (map? o) -
- - 016      (number? (:x o)) -
- - 017      (number? (:y o)) -
- - 018      (or (nil? (:z o)) (number? (:z o))))) -
- - 019   -
- - 020  (def ensure3d -
- - 021    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise -
- - 022    return a vertex like `o` but having thie `dflt` value as the value of its -
- - 023    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. -
- - 024   -
- - 025    If `o` is not a vertex, throws an exception." -
- - 026    (memoize -
- - 027      (fn -
- - 028        ([o] -
- - 029         (ensure3d o 0.0)) -
- - 030        ([o dflt] + 008    [o]
- 031         (cond + 009    (cond
- - 032           (not (vertex? o)) (throw (Exception. "Not a vertex!")) + + 010      (and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}"))
- - 033           (:z o) o + + 011      (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}"))
- - 034           :else (assoc o :z dflt)))))) + + 012      :else (throw (Exception. "Not a vertex."))))
- 035   + 013  
- 036  (def ensure2d + 014  (defn vertex?
- 037    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." + 015    "True if `o` satisfies the conditions for a vertex. That is, essentially,
- - 038    (memoize + + 016    that it must rerpresent a two- or three- dimensional vector. A vertex is +
+ + 017    shall be a map having at least the keys `:x` and `:y`, where the value of +
+ + 018    those keys is a number. If the key `:z` is also present, its value must also +
+ + 019    be a number. +
+ + 020   +
+ + 021    The name  `vector?` was not used as that would clash with a function of that +
+ + 022    name in `clojure.core` whose semantics are entirely different." +
+ + 023    [o] +
+ + 024    (and +
+ + 025      (map? o) +
+ + 026      (:id o) +
+ + 027      (number? (:x o)) +
+ + 028      (number? (:y o)) +
+ + 029      (or (nil? (:z o)) (number? (:z o))) +
+ + 030      (or (nil? (:kind o)) (= (:kind o) :vertex)))) +
+ + 031  
- 039      (fn [o] + 032  (defn make-vertex
- - 040        (if + + 033    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
- - 041          (vertex? o) + + 034    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`. +
+ + 035    It's not necessary to use this function to create a vertex, but the `:id` +
+ + 036    must be present and must be unique." +
+ + 037    ([x y] +
+ + 038     (let [v {:x x :y y :kind :vertex}] +
+ + 039       (assoc v :id (vertex-key v)))) +
+ + 040    ([x y z] +
+ + 041     (assoc (make-vertex x y) :z z))) +
+ + 042   +
+ + 043  (def ensure3d +
+ + 044    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise +
+ + 045    return a vertex like `o` but having thie `dflt` value as the value of its +
+ + 046    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. +
+ + 047   +
+ + 048    If `o` is not a vertex, throws an exception." +
+ + 049    (memoize +
+ + 050      (fn +
+ + 051        ([o] +
+ + 052         (ensure3d o 0.0)) +
+ + 053        ([o dflt] +
+ + 054         (cond +
+ + 055           (not (vertex? o)) (throw (Exception. "Not a vertex!")) +
+ + 056           (:z o) o
- 042          (assoc o :z 0.0) + 057           :else (assoc o :z dflt)))))) +
+ + 058   +
+ + 059  (def ensure2d +
+ + 060    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." +
+ + 061    (memoize +
+ + 062      (fn [o] +
+ + 063        (if
- 043          (throw (Exception. "Not a vertex!")))))) + 064          (vertex? o) +
+ + 065          (assoc o :z 0.0) +
+ + 066          (throw (Exception. "Not a vertex!"))))))
diff --git a/docs/codox/dali-performance.html b/docs/codox/dali-performance.html new file mode 100644 index 0000000..f90fcc1 --- /dev/null +++ b/docs/codox/dali-performance.html @@ -0,0 +1,171 @@ + +Dali performance

Dali performance

+

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

+

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

+

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

+
(defn- facet->svg-poly
+  [facet]
+  [:polygon
+   {:points (s/join " " (map #(str (:x %) "," (:y %)) (:vertices facet)))}])
+
+

we get this performance using the smaller isle_of_man map:

+
walkmap.svg=> (def ^:dynamic *preferred-svg-render* :hiccup)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def hiccup (binary-stl-file->svg "resources/isle_of_man.stl" "resources/isle_of_man.svg")))
+20-05-25 09:21:43 mason INFO [walkmap.svg:82] - Generating SVG for  :hiccup  renderer
+20-05-25 09:21:43 mason INFO [walkmap.svg:96] - Emitting SVG with  :hiccup  renderer
+"Elapsed time: 86.904891 msecs"
+#'walkmap.svg/hiccup
+walkmap.svg=> (def ^:dynamic *preferred-svg-render* :dali)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def dali (binary-stl-file->svg "resources/isle_of_man.stl" "resources/isle_of_man.svg")))
+20-05-25 09:22:17 mason INFO [walkmap.svg:82] - Generating SVG for  :dali  renderer
+20-05-25 09:22:17 mason INFO [walkmap.svg:96] - Emitting SVG with  :dali  renderer
+"Elapsed time: 890.863814 msecs"
+#'walkmap.svg/dali
+
+

If we switch the Dali render to use my original version of facet->svg-poly, i.e. this one:

+
(defn- dali-facet->svg-poly
+  [facet]
+  (vec
+    (cons
+      :polygon
+      (map #(vec (list (:x %) (:y %))) (:vertices facet)))))
+
+

we get this performance:

+
walkmap.svg=> (def ^:dynamic *preferred-svg-render* :hiccup)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def hiccup (binary-stl-file->svg "resources/isle_of_man.stl" "resources/isle_of_man.svg")))
+20-05-25 09:35:33 mason INFO [walkmap.svg:82] - Generating SVG for  :hiccup  renderer
+20-05-25 09:35:33 mason INFO [walkmap.svg:96] - Emitting SVG with  :hiccup  renderer
+"Elapsed time: 84.09972 msecs"
+#'walkmap.svg/hiccup
+walkmap.svg=> (def ^:dynamic *preferred-svg-render* :dali)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def dali (binary-stl-file->svg "resources/isle_of_man.stl" "resources/isle_of_man.svg")))
+20-05-25 09:35:41 mason INFO [walkmap.svg:82] - Generating SVG for  :dali  renderer
+20-05-25 09:35:41 mason INFO [walkmap.svg:96] - Emitting SVG with  :dali  renderer
+"Elapsed time: 874.292007 msecs"
+#'walkmap.svg/dali
+
+

No significant difference in performance.

+

If we generate but don’t render, we get this:

+
walkmap.svg=> (def ^:dynamic *preferred-svg-render* :hiccup)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def hiccup (binary-stl-file->svg "resources/isle_of_man.stl")))
+20-05-25 09:37:44 mason INFO [walkmap.svg:82] - Generating SVG for  :hiccup  renderer
+"Elapsed time: 52.614707 msecs"
+#'walkmap.svg/hiccup
+walkmap.svg=> (def ^:dynamic *preferred-svg-render* :dali)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (def dali (binary-stl-file->svg "resources/isle_of_man.stl")))
+20-05-25 09:38:07 mason INFO [walkmap.svg:82] - Generating SVG for  :dali  renderer
+"Elapsed time: 49.891043 msecs"
+#'walkmap.svg/dali
+
+

This implies that the problem is not in the way polygons are formatted.

+

The difference between the two versions of facet->svg-poly is as follows:

+

New version, works with both Hiccup and Dali:

+
walkmap.svg=> (def stl (decode-binary-stl "resources/isle_of_man.stl"))
+#'walkmap.svg/stl
+walkmap.svg=> (def facet (first (:facets stl)))
+#'walkmap.svg/facet
+walkmap.svg=> (pprint facet)
+{:normal {:x -0.0, :y 0.0, :z 1.0},
+ :vertices
+ [{:x 3.0, :y 1.0, :z 1.0}
+  {:x 2.0, :y 3.0, :z 1.0}
+  {:x 0.0, :y 0.0, :z 1.0}],
+ :abc 0}
+nil
+walkmap.svg=> (pprint (facet->svg-poly facet))
+[:polygon {:points "3.0,1.0 2.0,3.0 0.0,0.0"}]
+nil
+
+

In other words, the new version constructs the :points attribute of the :polygon tag by string concatenation, and the renderer just needs to output it.

+

Older version, works with Dali only:

+
walkmap.svg=> (pprint (dali-facet->svg-poly facet))
+[:polygon [3.0 1.0] [2.0 3.0] [0.0 0.0]]
+nil
+
+

This means that the renderer is actually doing more work, since it has to compose the :points attribute itself; nevertheless there doesn’t seem to be an increased time penalty.

+

Conclusion

+

It doesn’t seem that formatting the polygons is the issue.

+

Hypothesis two: Dali renderer scales non-linearly with number of objects drawn

+

To test this, we need some otherwise-similar test files with different numbers of objects:

+
walkmap.svg=> (count (:facets stl))
+4416
+walkmap.svg=> (def small-stl (assoc stl :facets (take 400 (:facets stl))))
+#'walkmap.svg/small-stl
+walkmap.svg=> (count (:facets small-stl))
+400
+walkmap.svg=> (def large-stl (decode-binary-stl "../the-great-game/resources/maps/heightmap.stl"))
+#'walkmap.svg/large-stl
+walkmap.svg=> (count (:facets large-stl))
+746585
+walkmap.svg=> (def ^:dynamic *preferred-svg-render* :dali)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (dali.io/render-svg (stl->svg small-stl) "dali-small.svg"))
+20-05-25 10:12:25 mason INFO [walkmap.svg:92] - Generating SVG for  :dali  renderer
+"Elapsed time: 32.55506 msecs"
+nil
+walkmap.svg=> (def ^:dynamic *preferred-svg-render* :hiccup)
+#'walkmap.svg/*preferred-svg-render*
+walkmap.svg=> (time (spit "hiccup-small.svg" (hiccup.core/html (stl->svg small-stl))))
+20-05-25 10:14:07 mason INFO [walkmap.svg:92] - Generating SVG for  :hiccup  renderer
+"Elapsed time: 10.026369 msecs"
+
+

So we have

+ + + + + + + + + + + + + + + + + + + + + +
Dali Hiccup
# of facets time (msecs) objets/msec time (msecs) objets/msec ratio (Dali/Hiccup)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
400 32.55506 12.29 10.026369 39.89 3.35
4416 874.292007 5.05 84.09972 52.51 10.40
746585 29,695,695.61 0.03 16724.848222 44.64 1775.54
+

Conclusion

+

What we’re seeing is that Hiccup renders more or less linearly by the number of objects (bear in mind that all of these objects are triangles, so essentially equally complex to render), whereas trhe performance of Dali degrades significantly as the number of objects increases.

\ No newline at end of file diff --git a/docs/codox/index.html b/docs/codox/index.html index cbfce57..2fd020f 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:

[walkmap "0.1.0-SNAPSHOT"]

Topics

Namespaces

walkmap.core

At this stage, primarily utility functions dealing with stereolithography (STL) files. Not a stable API yet!

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.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.svg

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

Public variables and functions:

walkmap.vertex

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

Public variables and functions:

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

Walkmap 0.1.0-SNAPSHOT

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

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

Installation

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

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

Topics

Namespaces

walkmap.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.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/intro.html b/docs/codox/intro.html index e19fc96..8e5f50e 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to walkmap

    Introduction to walkmap

    +Introduction to walkmap

    Introduction to walkmap

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

    Usage

    What works:

    @@ -10,6 +10,10 @@

    Lein dependency:

    [walkmap "0.1.0-SNAPSHOT"]
     
    +

    Converting heightmaps to STL

    Doesn’t work yet, and is not a priority. Use hmm instead.

    Reading binary STL files

    @@ -36,10 +40,19 @@
    (require '[walkmap.core :refer [binary-stl-file->svg]])
     (binary-stl-file->svg "path/to/input-file.stl" "path-to-output-file.svg")
     
    -

    As above, but, as a side effect, writes the SVG to the specified output file. Works for smaller test files, as above.

    +

    As above, but, as a side effect, writes the SVG to the specified output file.

    Merging exclusion maps and reserved area maps

    It is intended that it should be possible to merge exclusion maps (maps of areas which should be excluded from the traversable area) with maps derived from height maps. These exclusion maps will probably be represented as SVG.

    This is not yet implemented.

    +

    Culling facets in ocean areas is implemented and works:

    +
    (require '[walkmap.core :refer [cull-ocean-facets *sea-level*]])
    +(cull-ocean-facets stl)
    +
    +

    If sea level in your heightmaps is not zero, e.g. is 5, set it thus:

    +
    (def ^:dynamic *sea-level* 5.0)
    +(cull-ocean-facets stl)
    +
    +

    It is strongly recomended that you set *sea-level* to a floating point number, not an integer, because numbers are specified in the STL file as floating point, and in Clojure, (= 5 5.0) returns false.

    Merging road maps and river system maps

    It is intended that it should be possible to merge road maps (maps of already computed routes) with maps derived from height maps. These exclusion maps will probably be represented as SVG. This is not yet implemented.

    River system maps are conceptually similar to road maps; this too is not yet implemented.

    diff --git a/docs/codox/walkmap.core.html b/docs/codox/walkmap.core.html index 1ce46d2..4dbd673 100644 --- a/docs/codox/walkmap.core.html +++ b/docs/codox/walkmap.core.html @@ -1,4 +1,3 @@ -walkmap.core documentation

    walkmap.core

    At this stage, primarily utility functions dealing with stereolithography (STL) files. Not a stable API yet!

    *sea-level*

    dynamic

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

    -

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

    binary-stl-file->svg

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

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

    cull-ocean-facets

    (cull-ocean-facets stl)

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

    ocean?

    (ocean? facet)

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

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

    walkmap.core

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

    \ No newline at end of file diff --git a/docs/codox/walkmap.edge.html b/docs/codox/walkmap.edge.html index 49fe8ac..fa1c0b8 100644 --- a/docs/codox/walkmap.edge.html +++ b/docs/codox/walkmap.edge.html @@ -1,3 +1,3 @@ -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.

    edge?

    (edge? o)

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

    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.

    \ 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.

    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 path. A path 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.

    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 fbb6833..bd7067c 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

    collinear?

    (collinear? v1 v2 v3)

    True if these vertices v1, v2, v3 are colinear; false otherwise.

    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

    on?

    (on? e v)

    True if the vertex v is on the edge e.

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

    walkmap.ocean

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

    *sea-level*

    dynamic

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

    +

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

    cull-ocean-facets

    (cull-ocean-facets stl)

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

    ocean?

    (ocean? facet)

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

    \ No newline at end of file diff --git a/docs/codox/walkmap.path.html b/docs/codox/walkmap.path.html index 95180b3..0a63179 100644 --- a/docs/codox/walkmap.path.html +++ b/docs/codox/walkmap.path.html @@ -1,4 +1,4 @@ -walkmap.path documentation

    walkmap.path

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

    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.polygon.

    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.

    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 diff --git a/docs/codox/walkmap.polygon.html b/docs/codox/walkmap.polygon.html index b829023..c3c2657 100644 --- a/docs/codox/walkmap.polygon.html +++ b/docs/codox/walkmap.polygon.html @@ -1,3 +1,3 @@ -walkmap.polygon documentation

    walkmap.polygon

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

    polygon?

    (polygon? o)

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

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

    walkmap.polygon

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

    polygon?

    (polygon? o)

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

    \ No newline at end of file diff --git a/docs/codox/walkmap.stl.html b/docs/codox/walkmap.stl.html index eb281c8..271e681 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.

    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)

    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 diff --git a/docs/codox/walkmap.superstructure.html b/docs/codox/walkmap.superstructure.html new file mode 100644 index 0000000..0dbecd5 --- /dev/null +++ b/docs/codox/walkmap.superstructure.html @@ -0,0 +1,19 @@ + +walkmap.superstructure documentation

    walkmap.superstructure

    single indexing structure for walkmap objects

    add-to-superstructure

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

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

    +

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

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

    index-vertex

    (index-vertex s o v)

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

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

    index-vertices

    (index-vertices s o)

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

    +
      +
    1. s is not a map;
    2. +
    3. o is not a map;
    4. +
    5. o does not have a value for the key :id.
    6. +
    \ No newline at end of file diff --git a/docs/codox/walkmap.svg.html b/docs/codox/walkmap.svg.html index 22e1c10..2f47187 100644 --- a/docs/codox/walkmap.svg.html +++ b/docs/codox/walkmap.svg.html @@ -1,3 +1,3 @@ -walkmap.svg documentation

    walkmap.svg

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

    stl->svg

    (stl->svg stl)

    Convert this in-memory stl structure, as read by decode-binary-stl, into an in-memory (Dali) SVG structure, and return it.

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

    walkmap.svg

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

    *preferred-svg-render*

    dynamic

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

    binary-stl-file->svg

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

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

    dali-stl->svg

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

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

    hiccup-stl->svg

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

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

    stl->svg

    (stl->svg stl)

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

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

    walkmap.utils

    Miscellaneous utility functions.

    deep-merge

    (deep-merge & vals)

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

    vertices

    (vertices o)

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

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

    walkmap.vertex

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

    ensure2d

    (ensure2d o)

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

    ensure3d

    (ensure3d o)(ensure3d o dflt)

    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? 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.

    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 diff --git a/src/walkmap/BinaryToASCII.py b/src/walkmap/BinaryToASCII.py deleted file mode 100644 index a30ed3a..0000000 --- a/src/walkmap/BinaryToASCII.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- encoding: utf-8 -*- -# From https://github.com/IsseiMori/binary-stl-toASCII/blob/master/BinaryToASCII.py -# Included here to sanity check. -import struct - -infile = open('../the-great-game/resources/maps/heightmap.stl') #import file -out = open('ASCII.stl', 'w') #export file - -data = infile.read() - - -out.write("solid ") - -for x in xrange(0,80): - if not ord(data[x]) == 0: - out.write(struct.unpack('c', data[x])[0]) - else: - pass -out.write("\n") - -number = data[80] + data[81] + data[82] + data[83] -faces = struct.unpack('I',number)[0] - -for x in range(0,faces): - out.write("facet normal ") - - xc = data[84+x*50] + data[85+x*50] + data[86+x*50] + data[87+x*50] - yc = data[88+x*50] + data[89+x*50] + data[90+x*50] + data[91+x*50] - zc = data[92+x*50] + data[93+x*50] + data[94+x*50] + data[95+x*50] - - out.write(str(struct.unpack('f',xc)[0]) + " ") - out.write(str(struct.unpack('f',yc)[0]) + " ") - out.write(str(struct.unpack('f',zc)[0]) + "\n") - - out.write("outer loop\n") - - for y in range(1,4): - out.write("vertex ") - - xc = data[84+y*12+x*50] + data[85+y*12+x*50] + data[86+y*12+x*50] + data[87+y*12+x*50] - yc = data[88+y*12+x*50] + data[89+y*12+x*50] + data[90+y*12+x*50] + data[91+y*12+x*50] - zc = data[92+y*12+x*50] + data[93+y*12+x*50] + data[94+y*12+x*50] + data[95+y*12+x*50] - - out.write(str(struct.unpack('f',xc)[0]) + " ") - out.write(str(struct.unpack('f',yc)[0]) + " ") - out.write(str(struct.unpack('f',zc)[0]) + "\n") - - out.write("endloop\n") - out.write("endfacet\n") - -out.close() -print "end" diff --git a/src/walkmap/path.clj b/src/walkmap/path.clj index ee2aa07..1928d6e 100644 --- a/src/walkmap/path.clj +++ b/src/walkmap/path.clj @@ -13,7 +13,15 @@ (and (seq? v) (> (count v) 2) - (every? vertex? v)))) + (every? vertex? v) + (or (nil? (:kind o)) (= (:kind o) :path))))) + +(defn make-path + [nodes] + (if + (every? vertex? nodes) + {:nodes nodes :id (keyword (gensym "path")) :kind :path} + (throw (Exception. "Each item on path must be a vertex.")))) (defn polygon->path "If `o` is a polygon, return an equivalent path. What's different about @@ -25,6 +33,6 @@ [o] (if (polygon? o) - (assoc (dissoc o :vertices) :nodes (concat (:vertices o) (list (first (:vertices o))))) + (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) (throw (Exception. "Not a polygon!")))) diff --git a/src/walkmap/polygon.clj b/src/walkmap/polygon.clj index 71b3728..beaa78d 100644 --- a/src/walkmap/polygon.clj +++ b/src/walkmap/polygon.clj @@ -10,8 +10,9 @@ (let [v (:vertices o)] (and - (seq? v) + (coll? v) (> (count v) 2) - (every? vertex? v)))) + (every? vertex? v) + (or (nil? (:kind o)) (= (:kind o) :polygon))))) diff --git a/src/walkmap/stl.clj b/src/walkmap/stl.clj index e9c8789..fee15e3 100644 --- a/src/walkmap/stl.clj +++ b/src/walkmap/stl.clj @@ -5,7 +5,8 @@ [me.raynes.fs :as fs] [org.clojars.smee.binary.core :as b] [taoensso.timbre :as l :refer [info error spy]] - [walkmap.polygon :refer [polygon?]]) + [walkmap.polygon :refer [polygon?]] + [walkmap.vertex :refer [vertex-key]]) (:import org.clojars.smee.binary.core.BinaryIO java.io.DataInput)) @@ -26,6 +27,7 @@ (every? polygon? (:facets o)) (if (:header o) (string? (:header o)) true) (if (:count o) (integer? (:count o)) true) + (or (nil? (:kind o)) (= (:kind o) :stl)) (if verify-count? (= (:count o) (count (:facets o))) true)))) (def vect @@ -49,6 +51,26 @@ :count :uint-le :facets (b/repeated facet))) +(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) (assoc o :kind :vertex :id (or (:id o) (vertex-key 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. @@ -57,7 +79,7 @@ data, if it is not this will run but will return garbage." [filename] (let [in (io/input-stream filename)] - (b/decode binary-stl in))) + (canonicalise (b/decode binary-stl in)))) (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 new file mode 100644 index 0000000..80a514a --- /dev/null +++ b/src/walkmap/superstructure.clj @@ -0,0 +1,76 @@ +(ns walkmap.superstructure + "single indexing structure for walkmap objects" + (:require [walkmap.path :as p] + [walkmap.polygon :as q] + [walkmap.stl :as s] + [walkmap.utils :as u] + [walkmap.vertex :as v])) + +(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 + + 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." + ;; 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) + (if (v/vertex? v) + (let [vi (or (:vertex-index s) {}) + current (or (vi (:id v)) {})] + ;; deep-merge doesn't merge sets, only maps; so at this + ;; stage we need to build a map. + (assoc vi (:id v) (assoc current (:id o) (:id v)))) + (throw (Exception. "Not a vertex: " v))) + (throw (Exception. (subs (str "No `:id` value: " o) 0 80)))) + ;; it shouldn't actually be an error to try to index a vertex, but it + ;; also isn't useful to do so, so I'd be inclined to ignore it. + (:vertex-index s))) + +(defn index-vertices + "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`." + [s o] + (assoc + s + :vertex-index + (reduce + u/deep-merge + (map + #(index-vertex s o %) + (u/vertices o))))) + +(defn add-to-superstructure + "Return a superstructure like `s` with object `o` added. If `o` is a collection, + return a superstructure like `s` with each element of `o` added. If only one + argument is supplied it will be assumed to represent `o` and a new + superstructure will be returned. + + It is an error (and an exception may be thrown) if + + 1. `s` is not a map; + 2. `o` is not a map, or a sequence of maps." + ([o] + (add-to-superstructure {} o)) + ([s o] + (cond + (map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))] + (index-vertices (assoc s (:id o') o') o')) + (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o)) + (nil? o) o + :else + (throw (Exception. (str "Don't know how to index " (or (type o) "nil"))))))) + +(:vertex-index (add-to-superstructure (:facets (s/decode-binary-stl "resources/isle_of_man.stl")))) +(s/decode-binary-stl "resources/isle_of_man.stl") diff --git a/src/walkmap/utils.clj b/src/walkmap/utils.clj new file mode 100644 index 0000000..60b0977 --- /dev/null +++ b/src/walkmap/utils.clj @@ -0,0 +1,26 @@ +(ns walkmap.utils + "Miscellaneous utility functions." + (:require [walkmap.path :as p] + [walkmap.polygon :as q] + [walkmap.vertex :as v])) + +(defn deep-merge + "Recursively merges maps. If vals are not maps, the last value wins." + ;; TODO: not my implementation, not sure I entirely trust it. + [& vals] + (if (every? map? vals) + (apply merge-with deep-merge vals) + (last vals))) + +(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))) diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index c62d1ee..4c60945 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -1,6 +1,16 @@ (ns walkmap.vertex "Essentially the specification for things we shall consider to be vertices.") +(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`." + [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 (Exception. "Not a vertex.")))) + (defn vertex? "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 @@ -13,9 +23,22 @@ [o] (and (map? o) + (:id o) (number? (:x o)) (number? (:y o)) - (or (nil? (:z o)) (number? (:z o))))) + (or (nil? (:z o)) (number? (:z o))) + (or (nil? (:kind o)) (= (:kind o) :vertex)))) + +(defn make-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`. + It's not necessary to use this function to create a vertex, but the `:id` + must be present and must be unique." + ([x y] + (let [v {:x x :y y :kind :vertex}] + (assoc v :id (vertex-key v)))) + ([x y z] + (assoc (make-vertex x y) :z z))) (def ensure3d "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise diff --git a/test/walkmap/edge_test.clj b/test/walkmap/edge_test.clj index b919623..fe5865b 100644 --- a/test/walkmap/edge_test.clj +++ b/test/walkmap/edge_test.clj @@ -1,46 +1,53 @@ (ns walkmap.edge-test (:require [clojure.test :refer :all] - [walkmap.edge :refer :all])) + [walkmap.edge :refer :all] + [walkmap.vertex :refer [make-vertex]])) (deftest edge-test (testing "identification of edges." - (is (edge? {:start {:x 0.0 :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}}) "It is.") - (is (not (edge? {:start {:y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}})) "Start lacks :x key") - (is (not (edge? {:start {:x nil :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}})) "Start lacks :x value") - (is (not (edge? {:begin {:x nil :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}})) "Lacks start key") - (is (not (edge? {:start {:x nil :y 0.0 :z 0.0} :finish {:x 3 :y 4 :z 0.0}})) "Lacks end key") - (is (not (edge? {:start {:x "zero" :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}})) "Value of x in start is not a number") + (is (edge? {:start (make-vertex 0.0 0.0 0.0) + :end (make-vertex 3 4 0.0)}) "It is.") + (is (not (edge? {:start {:y 0.0 :z 0.0 :id 'foo} + :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Start lacks :x key") + (is (not (edge? {:start {:x nil :y 0.0 :z 0.0 :id 'foo} + :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Start lacks :x value") + (is (not (edge? {:begin {:x nil :y 0.0 :z 0.0 :id 'foo} + :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Lacks start key") + (is (not (edge? {:start {:x nil :y 0.0 :z 0.0 :id 'foo} + :finish {:x 3 :y 4 :z 0.0 :id 'bar}})) "Lacks end key") + (is (not (edge? {:start {:x "zero" :y 0.0 :z 0.0 :id 'foo} + :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Value of x in start is not a number") )) (deftest length-test (testing "length of an edge" - (is (= (length {:start {:x 0.0 :y 0.0 :z 0.0} :end {:x 3.0 :y 4.0 :z 0.0}}) 5.0)))) + (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} :end {:x 3 :y 4 :z 0.0}}) + (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} :end {:x 4.0 :y 6.0 :z 3.5}}) + (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 parallel-test (testing "parallelism" - (is (parallel? {:start {:x 0.0 :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}} - {:start {:x 1.0 :y 2.0 :z 3.5} :end {:x 4.0 :y 6.0 :z 3.5}}) + (is (parallel? {: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 be") (is (not - (parallel? {:start {:x 0.0 :y 0.0 :z 0.0} :end {:x 3 :y 4 :z 0.0}} - {:start {:x 1.0 :y 2.0 :z 3.5} :end {:x 4.0 :y 6.0 :z 3.49}})) + (parallel? {: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.49 :id 'bar}})) "Should not be!"))) (deftest collinear-test (testing "collinearity" - (is (collinear? {:start {:x 0.0 :y 0.0 :z 0.0} :end {:x 3.0 :y 4.0 :z 0.0}} - {:start {:x 3.0 :y 4.0 :z 0.0} :end {:x 9.0 :y 12.0 :z 0.0}}) + (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} :end {:x 3 :y 4 :z 0.0}} - {:start {:x 1.0 :y 2.0 :z 3.5} :end {:x 4.0 :y 6.0 :z 3.5}})) + (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!"))) diff --git a/test/walkmap/stl_test.clj b/test/walkmap/stl_test.clj new file mode 100644 index 0000000..4f6a105 --- /dev/null +++ b/test/walkmap/stl_test.clj @@ -0,0 +1,96 @@ +(ns walkmap.stl-test + (:require [clojure.test :refer :all] + [walkmap.stl :refer :all] + [walkmap.polygon :refer [polygon?]] + [walkmap.vertex :refer [vertex?]])) + +(deftest canonicalise-test + (testing "Canonicalisation of objects read from STL: vertices." + (is (vertex? (canonicalise {:x 3.0, :y 1.0, :z 1.0})) + "Vertex: should have an `:id` and `:kind` = `:vertex`.") + (is (= (:x (canonicalise {:x 3.0, :y 1.0, :z 1.0})) 3.0) + "`:x` value should be unchanged.") + (is (= (:y (canonicalise {:x 3.0, :y 1.0, :z 1.0})) 1.0) + "`:y` value should be unchanged.") + (is (= (:z (canonicalise {:x 3.0, :y 1.0, :z 1.0})) 1.0) + "`:z` value should be unchanged.") + (is (every? + vertex? + (canonicalise [{:x 3.0, :y 1.0, :z 1.0} + {:x 2.0, :y 3.0, :z 1.0} + {:x 0.0, :y 0.0, :z 1.0}])) + "Vertices: should recurse.")) + (testing "Canonicalisation of objects read from STL: facets/polygons." + (let [p {:normal {:x -0.0, :y 0.0, :z 1.0}, + :vertices [{:x 3.0, :y 1.0, :z 1.0} + {:x 2.0, :y 3.0, :z 1.0} + {:x 0.0, :y 0.0, :z 1.0}], + :abc 0} + p' (canonicalise p)] + (is (polygon? p') + "Polygon: should have an `:id` and `:kind` = `:polygon`.") + (is (= (count (:vertices p)) (count (:vertices p'))) + "Number of vertices should not change") + (map + #(is (= (map % (:vertices p))(map % (:vertices p'))) + (str "Order of vertices should not change: " %)) + [:x :y :z])) + (is (every? + polygon? + (canonicalise + [{:normal {:x -0.0, :y 0.0, :z 1.0}, + :vertices [{:x 3.0, :y 1.0, :z 1.0} + {:x 2.0, :y 3.0, :z 1.0} + {:x 0.0, :y 0.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 10.0, :y 4.0, :z 1.0} + {:x 22.0, :y 3.0, :z 1.0} + {:x 13.0, :y 5.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 26.0, :y 46.0, :z 1.0} + {:x 29.0, :y 49.0, :z 1.0} + {:x 31.0, :y 61.0, :z 1.0}], + :abc 0} + {:normal {:x -0.0, :y 0.0, :z 1.0}, + :vertices [{:x 16.0, :y 33.0, :z 1.0} + {:x 15.0, :y 35.0, :z 1.0} + {:x 13.0, :y 32.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 81.0, :y 0.0, :z 1.0} + {:x 54.0, :y 27.0, :z 1.0} + {:x 51.0, :y 20.0, :z 1.0}], + :abc 0}])) + "Facets/polygons: should recurse.")) + (testing "Canonicalisation of entire STL structure." + (let [stl {:header "Dummy test STL", + :count 5, + :facets [{:normal {:x -0.0, :y 0.0, :z 1.0}, + :vertices [{:x 3.0, :y 1.0, :z 1.0} + {:x 2.0, :y 3.0, :z 1.0} + {:x 0.0, :y 0.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 10.0, :y 4.0, :z 1.0} + {:x 22.0, :y 3.0, :z 1.0} + {:x 13.0, :y 5.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 26.0, :y 46.0, :z 1.0} + {:x 29.0, :y 49.0, :z 1.0} + {:x 31.0, :y 61.0, :z 1.0}], + :abc 0} + {:normal {:x -0.0, :y 0.0, :z 1.0}, + :vertices [{:x 16.0, :y 33.0, :z 1.0} + {:x 15.0, :y 35.0, :z 1.0} + {:x 13.0, :y 32.0, :z 1.0}], + :abc 0} + {:normal {:x 0.0, :y 0.0, :z 1.0}, + :vertices [{:x 81.0, :y 0.0, :z 1.0} + {:x 54.0, :y 27.0, :z 1.0} + {:x 51.0, :y 20.0, :z 1.0}], + :abc 0}]} + stl' (canonicalise stl)] + (is (stl? stl') "Stl: should have an `:id` and `:kind` = `:stl`.")))) From f4ca49f11b2bd4807a83bd8aad0d5ca9368df935 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 26 May 2020 11:40:44 +0100 Subject: [PATCH 2/5] Work on routing. --- docs/cloverage/index.html | 48 ++++----- docs/cloverage/walkmap/stl.clj.html | 6 +- .../cloverage/walkmap/superstructure.clj.html | 6 +- docs/cloverage/walkmap/vertex.clj.html | 101 ++++++++++++------ src/walkmap/edge.clj | 12 ++- src/walkmap/stl.clj | 4 +- src/walkmap/superstructure.clj | 8 +- src/walkmap/vertex.clj | 19 +++- 8 files changed, 130 insertions(+), 74 deletions(-) diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index f066158..fcfdf92 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -104,16 +104,16 @@ walkmap.stl
    215
    169
    -55.99 % + style="width:54.716981132075475%; + float:left;"> 203
    168
    +54.72 %
    29
    10
    30
    9
    37
    51.32 % @@ -170,26 +170,26 @@ walkmap.vertex
    167
    32
    -83.92 % + style="width:84.52380952380952%; + float:left;"> 213
    39
    +84.52 %
    20
    7
    6
    -81.82 % -66733 + style="width:64.28571428571429%; + float:left;"> 27
    8
    7
    +83.33 % +79842 Totals: -46.27 % +47.23 % -51.59 % +52.63 % diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html index 2c577c0..e6cd3a5 100644 --- a/docs/cloverage/walkmap/stl.clj.html +++ b/docs/cloverage/walkmap/stl.clj.html @@ -29,7 +29,7 @@ 008              [walkmap.polygon :refer [polygon?]]
    - 009              [walkmap.vertex :refer [vertex-key]]) + 009              [walkmap.vertex :refer [canonicalise-vertex]])
    010    (:import org.clojars.smee.binary.core.BinaryIO @@ -211,8 +211,8 @@ 069      ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?`
    - - 070      (:x o) (assoc o :kind :vertex :id (or (:id o) (vertex-key o))) + + 070      (:x o) (canonicalise-vertex o)
    071      ;; shouldn't happen diff --git a/docs/cloverage/walkmap/superstructure.clj.html b/docs/cloverage/walkmap/superstructure.clj.html index 27266ac..fe56429 100644 --- a/docs/cloverage/walkmap/superstructure.clj.html +++ b/docs/cloverage/walkmap/superstructure.clj.html @@ -95,10 +95,10 @@ 030            (assoc vi (:id v) (assoc current (:id o) (:id v))))
    - 031          (throw (Exception. "Not a vertex: " v))) + 031          (throw (IllegalArgumentException. "Not a vertex: " v)))
    - 032        (throw (Exception. (subs (str "No `:id` value: " o) 0 80)))) + 032        (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 @@ -221,7 +221,7 @@ 072      :else
    - 073      (throw (Exception. (str "Don't know how to index " (or (type o) "nil"))))))) + 073      (throw (IllegalArgumentException. (str "Don't know how to index " (or (type o) "nil")))))))
    074   diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html index 538fa98..3ba073f 100644 --- a/docs/cloverage/walkmap/vertex.clj.html +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -38,7 +38,7 @@ 011      (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}"))
    - 012      :else (throw (Exception. "Not a vertex.")))) + 012      :else (throw (IllegalArgumentException. "Not a vertex."))))
    013   @@ -131,76 +131,115 @@ 042  
    - 043  (def ensure3d + 043  (defn canonicalise-vertex
    - 044    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise + 044    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
    - 045    return a vertex like `o` but having thie `dflt` value as the value of its + 045    upgrade it to something we will recognise as a vertex."
    - 046    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. -
    - - 047   -
    - - 048    If `o` is not a vertex, throws an exception." -
    - - 049    (memoize + 046    [o]
    - 050      (fn + 047    (if +
    + + 048      (and +
    + + 049        (map? o) +
    + + 050        (number? (:x o)) +
    + + 051        (number? (:y o)) +
    + + 052        (or (nil? (:z o)) (number? (:z o)))) +
    + + 053      (assoc o :kind :vertex :id (vertex-key o)) +
    + + 054      (throw (IllegalArgumentException. "Not a vertex.")))) +
    + + 055   +
    + + 056  (def ensure3d
    - 051        ([o] + 057    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise +
    + + 058    return a vertex like `o` but having thie `dflt` value as the value of its +
    + + 059    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. +
    + + 060   +
    + + 061    If `o` is not a vertex, throws an exception." +
    + + 062    (memoize +
    + + 063      (fn +
    + + 064        ([o]
    - 052         (ensure3d o 0.0)) + 065         (ensure3d o 0.0))
    - 053        ([o dflt] + 066        ([o dflt]
    - 054         (cond + 067         (cond
    - 055           (not (vertex? o)) (throw (Exception. "Not a vertex!")) + 068           (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!"))
    - 056           (:z o) o + 069           (:z o) o
    - 057           :else (assoc o :z dflt)))))) + 070           :else (assoc o :z dflt))))))
    - 058   + 071  
    - 059  (def ensure2d + 072  (def ensure2d
    - 060    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." + 073    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
    - 061    (memoize + 074    (memoize
    - 062      (fn [o] + 075      (fn [o]
    - 063        (if + 076        (if
    - 064          (vertex? o) + 077          (vertex? o)
    - 065          (assoc o :z 0.0) + 078          (assoc o :z 0.0)
    - 066          (throw (Exception. "Not a vertex!")))))) + 079          (throw (IllegalArgumentException. "Not a vertex!"))))))
    diff --git a/src/walkmap/edge.clj b/src/walkmap/edge.clj index 9f50281..356414b 100644 --- a/src/walkmap/edge.clj +++ b/src/walkmap/edge.clj @@ -8,7 +8,7 @@ [walkmap.vertex :refer [ensure3d vertex?]])) (defn edge? - "True if `o` satisfies the conditions for a path. A path shall be a map + "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." [o] @@ -19,7 +19,10 @@ (defn path->edges "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of - edges representing that path, polygon or sequence." + edges representing that path, polygon or sequence. + + Throws `IllegalArgumentException` if `o` is not a path, a polygon, or + sequence of vertices." [o] (cond (seq? o) @@ -34,7 +37,10 @@ (path? o) (path->edges (:nodes o)) (polygon? o) - (path->edges (polygon->path o)))) + (path->edges (polygon->path o)) + :else + (throw (IllegalArgumentException. + "Not a path, polygon, or sequence of vertices!")))) (defn length "Return the length of the edge `e`." diff --git a/src/walkmap/stl.clj b/src/walkmap/stl.clj index fee15e3..accbb09 100644 --- a/src/walkmap/stl.clj +++ b/src/walkmap/stl.clj @@ -6,7 +6,7 @@ [org.clojars.smee.binary.core :as b] [taoensso.timbre :as l :refer [info error spy]] [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :refer [vertex-key]]) + [walkmap.vertex :refer [canonicalise-vertex]]) (:import org.clojars.smee.binary.core.BinaryIO java.io.DataInput)) @@ -67,7 +67,7 @@ :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) (assoc o :kind :vertex :id (or (:id o) (vertex-key o))) + (:x o) (canonicalise-vertex o) ;; shouldn't happen :else o)) diff --git a/src/walkmap/superstructure.clj b/src/walkmap/superstructure.clj index 80a514a..b41c459 100644 --- a/src/walkmap/superstructure.clj +++ b/src/walkmap/superstructure.clj @@ -28,8 +28,8 @@ ;; deep-merge doesn't merge sets, only maps; so at this ;; stage we need to build a map. (assoc vi (:id v) (assoc current (:id o) (:id v)))) - (throw (Exception. "Not a vertex: " v))) - (throw (Exception. (subs (str "No `:id` value: " o) 0 80)))) + (throw (IllegalArgumentException. "Not a vertex: " v))) + (throw (IllegalArgumentException. (subs (str "No `:id` value: " o) 0 80)))) ;; it shouldn't actually be an error to try to index a vertex, but it ;; also isn't useful to do so, so I'd be inclined to ignore it. (:vertex-index s))) @@ -70,7 +70,5 @@ (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o)) (nil? o) o :else - (throw (Exception. (str "Don't know how to index " (or (type o) "nil"))))))) + (throw (IllegalArgumentException. (str "Don't know how to index " (or (type o) "nil"))))))) -(:vertex-index (add-to-superstructure (:facets (s/decode-binary-stl "resources/isle_of_man.stl")))) -(s/decode-binary-stl "resources/isle_of_man.stl") diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index 4c60945..6a01de2 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -9,7 +9,7 @@ (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 (Exception. "Not a vertex.")))) + :else (throw (IllegalArgumentException. "Not a vertex.")))) (defn vertex? "True if `o` satisfies the conditions for a vertex. That is, essentially, @@ -40,6 +40,19 @@ ([x y z] (assoc (make-vertex x y) :z z))) +(defn canonicalise-vertex + "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, + upgrade it to something we will recognise as a vertex." + [o] + (if + (and + (map? o) + (number? (:x o)) + (number? (:y o)) + (or (nil? (:z o)) (number? (:z o)))) + (assoc o :kind :vertex :id (vertex-key o)) + (throw (IllegalArgumentException. "Not a vertex.")))) + (def ensure3d "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise return a vertex like `o` but having thie `dflt` value as the value of its @@ -52,7 +65,7 @@ (ensure3d o 0.0)) ([o dflt] (cond - (not (vertex? o)) (throw (Exception. "Not a vertex!")) + (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!")) (:z o) o :else (assoc o :z dflt)))))) @@ -63,4 +76,4 @@ (if (vertex? o) (assoc o :z 0.0) - (throw (Exception. "Not a vertex!")))))) + (throw (IllegalArgumentException. "Not a vertex!")))))) From 9ee365b987f9ac24963efed99def149bdc562e96 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 26 May 2020 17:08:00 +0100 Subject: [PATCH 3/5] In the afternoon, he implemented tagging. And he looked on his work, and saw that it was good. --- src/walkmap/routing.clj | 18 ++++++++++++++ src/walkmap/tag.clj | 50 +++++++++++++++++++++++++++++++++++++++ test/walkmap/tag_test.clj | 45 +++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/walkmap/routing.clj create mode 100644 src/walkmap/tag.clj create mode 100644 test/walkmap/tag_test.clj diff --git a/src/walkmap/routing.clj b/src/walkmap/routing.clj new file mode 100644 index 0000000..b925341 --- /dev/null +++ b/src/walkmap/routing.clj @@ -0,0 +1,18 @@ +(ns walkmap.core + "Finding optimal routes to traverse a map." + (:require [walkmap.path :as p] + [walkmap.polygon :as q] + [walkmap.stl :as s] + [walkmap.utils :as u] + [walkmap.vertex :as v])) + +;; Breadth first search is a good algorithm for terrain in which all steps have +;; equal, but in our world (like the real world), they don't. + +;; Reading list: +;; +;; https://en.wikipedia.org/wiki/A*_search_algorithm +;; https://www.redblobgames.com/pathfinding/a-star/introduction.html +;; https://faculty.nps.edu/ncrowe/opmpaper2.htm +;; +;; See https://simon-brooke.github.io/the-great-game/codox/Pathmaking.html diff --git a/src/walkmap/tag.clj b/src/walkmap/tag.clj new file mode 100644 index 0000000..aacab67 --- /dev/null +++ b/src/walkmap/tag.clj @@ -0,0 +1,50 @@ +(ns 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." + (:require [clojure.set :refer [difference union]])) + +(defn tagged? + "True if this `object` is tagged with each of these `tags`." + [object & tags] + (if + (map? object) + (if + (every? keyword? tags) + (let [ot (::tags object)] + (and + (set? ot) + (every? ot tags) + true)) + (throw (IllegalArgumentException. + (str "Must be keyword(s): " (map type tags))))) + (throw (IllegalArgumentException. + (str "Must be a map: " (type object)))))) + +(defn tag + "Return an object like this `object` but with these `tags` added to its tags, + if they are not already present." + [object & tags] + (if + (map? object) + (if + (every? keyword? tags) + (assoc object ::tags (union (set tags) (::tags object))) + (throw (IllegalArgumentException. + (str "Must be keyword(s): " (map type tags))))) + (throw (IllegalArgumentException. + (str "Must be a map: " (type object)))))) + +(defn untag + "Return an object like this `object` but with these `tags` removed from its + tags, if present." + [object & tags] + (if + (map? object) + (if + (every? keyword? tags) + (assoc object ::tags (difference (::tags object) (set tags))) + (throw (IllegalArgumentException. + (str "Must be keywords: " (map type tags))))) + (throw (IllegalArgumentException. + (str "Must be a map: " (type object)))))) diff --git a/test/walkmap/tag_test.clj b/test/walkmap/tag_test.clj new file mode 100644 index 0000000..9f0a269 --- /dev/null +++ b/test/walkmap/tag_test.clj @@ -0,0 +1,45 @@ +(ns walkmap.tag-test + (:require [clojure.test :refer :all] + [walkmap.tag :refer :all])) + +(deftest tag-tests + (testing "Tagging" + (is (set? (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz))) + "The value of `:walkmap.tag/tags should be a set.") + (is (= (count (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz))) 4) + "All the tags passed should be added.") + (is (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz) :ban) + "`:ban` should be present in the set, and, as it is a set, it + should be valid to apply it to a keyword.") + (is (not ((:walkmap.tag/tags (tag {} :foo :bar :ban :froboz)) :cornflakes)) + "`:cornflakes should not be present.") + (is (true? (tagged? (tag {} :foo :bar :ban :froboz) :bar)) + "`tagged?` should return an explicit `true`, not any other value.") + (is (tagged? (tag {} :foo :bar :ban :froboz) :bar :froboz) + "We should be able to test for the presence of more than one tag") + (is (= (tagged? (tag {} :foo :bar :ban :froboz) :bar :cornflakes) false) + "If any of the queried tags is missing, false should be returned") + (is (tagged? (tag (tag {} :foo) :bar) :foo :bar) + "We should be able to add tags to an already tagged object") + (is (false? (tagged? (tag {} :foo :bar) :cornflakes)) + "`tagged?` should return an explicit `false` if a queried tag is missing.") + (let [object (tag {} :foo :bar :ban :froboz)] + (is (= (untag object :cornflakes) object) + "Removing a missing tag should have no effect.") + (is (tagged? (untag object :foo) :bar :ban :froboz) + "All tags not explicitly removed should still be present.") + (is (false? (tagged? (untag object :bar) :bar)) + "But the tag which has been removed should be removed.")) + (is (thrown? IllegalArgumentException (tag [] :foo)) + "An exception should be thrown if `object` is not a map: `tag`.") + (is (thrown? IllegalArgumentException (tagged? [] :foo)) + "An exception should be thrown if `object` is not a map: `tagged?`.") + (is (thrown? IllegalArgumentException (untag [] :foo)) + "An exception should be thrown if `object` is not a map: `untag`.") + (is (thrown? IllegalArgumentException (tag {} :foo "bar" :ban)) + "An exception should be thrown if any of `tags` is not a keyword: `tag`.") + (is (thrown? IllegalArgumentException (tagged? {} :foo "bar" :ban)) + "An exception should be thrown if any of `tags` is not a keyword: `tagged?`.") + (is (thrown? IllegalArgumentException (untag {} :foo "bar" :ban)) + "An exception should be thrown if any of `tags` is not a keywordp: `untag`."))) + From 79174af2c17de2f009c9feeb3646cad6e1061696 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 26 May 2020 23:30:07 +0100 Subject: [PATCH 4/5] #3 Inching forward, positively --- docs/cloverage/index.html | 145 ++++++------ docs/cloverage/walkmap/edge.clj.html | 210 ++++++++---------- docs/cloverage/walkmap/path.clj.html | 190 ++++++++++++---- docs/cloverage/walkmap/stl.clj.html | 8 +- .../cloverage/walkmap/superstructure.clj.html | 42 ++-- docs/cloverage/walkmap/tag.clj.html | 203 +++++++++++++++++ docs/cloverage/walkmap/utils.clj.html | 10 +- docs/cloverage/walkmap/vertex.clj.html | 165 +++++++------- docs/codox/dali-performance.html | 2 +- docs/codox/index.html | 2 +- docs/codox/intro.html | 2 +- docs/codox/walkmap.core.html | 2 +- docs/codox/walkmap.edge.html | 3 +- docs/codox/walkmap.geometry.html | 2 +- docs/codox/walkmap.ocean.html | 2 +- docs/codox/walkmap.path.html | 2 +- docs/codox/walkmap.polygon.html | 2 +- docs/codox/walkmap.stl.html | 2 +- docs/codox/walkmap.superstructure.html | 2 +- docs/codox/walkmap.svg.html | 2 +- docs/codox/walkmap.tag.html | 15 ++ docs/codox/walkmap.utils.html | 2 +- docs/codox/walkmap.vertex.html | 4 +- src/walkmap/edge.clj | 34 +-- src/walkmap/path.clj | 56 ++++- src/walkmap/stl.clj | 4 +- src/walkmap/tag.clj | 25 ++- src/walkmap/vertex.clj | 13 +- test/walkmap/edge_test.clj | 17 +- test/walkmap/tag_test.clj | 8 +- 30 files changed, 766 insertions(+), 410 deletions(-) create mode 100644 docs/cloverage/walkmap/tag.clj.html create mode 100644 docs/codox/walkmap.tag.html diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index fcfdf92..e26e161 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -27,20 +27,14 @@ walkmap.edge
    136
    60
    -69.39 % + style="width:100.0%; + float:left;"> 164
    +100.00 %
    35
    1
    14
    -72.00 % -82750 + style="width:100.0%; + float:left;"> 40
    +100.00 % +70740 walkmap.geometry
    walkmap.path
    4
    91
    -4.21 % + style="width:3.5294117647058822%; + float:left;"> 6
    164
    +3.53 %
    4
    15
    -21.05 % -38519 + style="width:15.789473684210526%; + float:left;"> 6
    32
    +15.79 % +76738 walkmap.polygon
    walkmap.stl
    203
    168
    -54.72 % + style="width:52.02156334231806%; + float:left;"> 193
    178
    +52.02 %
    30
    28
    9
    37
    -51.32 % + style="width:51.31578947368421%; + float:left;"> 39 +48.68 % 1481376 walkmap.superstructure
    119
    47
    -71.69 % -
    20
    4
    5
    -82.76 % -76829 + style="width:97.40259740259741%; + float:left;"> 150 +2.60 % +
    4
    23
    +14.81 % +74827 walkmap.svg
    108766 - walkmap.utils
    24
    14
    -63.16 % + walkmap.tag
    137
    +100.00 %
    6
    2
    2
    -80.00 % + style="width:100.0%; + float:left;"> 36
    +100.00 % +65736 + + + walkmap.utils
    3
    35
    +7.89 % +
    3
    7
    +30.00 % 26210 walkmap.vertex
    213
    39
    -84.52 % + style="width:84.92063492063492%; + float:left;"> 214
    38
    +84.92 %
    27
    8
    28
    7
    7
    83.33 % -79842 +82942 Totals: -47.23 % +44.01 % -52.63 % +50.55 % diff --git a/docs/cloverage/walkmap/edge.clj.html b/docs/cloverage/walkmap/edge.clj.html index 280afe2..0d75b13 100644 --- a/docs/cloverage/walkmap/edge.clj.html +++ b/docs/cloverage/walkmap/edge.clj.html @@ -20,235 +20,199 @@ 005    (:require [clojure.math.numeric-tower :as m]

    - 006              [walkmap.path :refer [path? polygon->path]] + 006              [walkmap.polygon :refer [polygon?]]
    - 007              [walkmap.polygon :refer [polygon?]] -
    - - 008              [walkmap.vertex :refer [ensure3d vertex?]])) + 007              [walkmap.vertex :refer [ensure3d vertex?]]))
    - 009   + 008  
    - 010  (defn edge? + 009  (defn edge
    - 011    "True if `o` satisfies the conditions for a path. A path shall be a map + 010    "Return an edge between vertices `v1` and `v2`."
    - 012    having the keys `:start` and `:end`, such that the values of each of those + 011    [v1 v2]
    - - 013    keys shall be a vertex." + + 012    (if
    - - 014    [o] + + 013      (and (vertex? v1) (vertex? v2))
    - - 015    (and + + 014      {:kind :edge :id (keyword (gensym "edge")) :start v1 :end v2}
    - 016      (map? o) -
    - - 017      (vertex? (:start o)) -
    - - 018      (vertex? (:end o)))) + 015      (throw (IllegalArgumentException. "Must be vertices."))))
    - 019   + 016  
    - 020  (defn path->edges + 017  (defn edge?
    - 021    "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of + 018    "True if `o` satisfies the conditions for a edge. An edge shall be a map
    - 022    edges representing that path, polygon or sequence." + 019    having the keys `:start` and `:end`, such that the values of each of those
    - 023    [o] + 020    keys shall be a vertex."
    - - 024    (cond + + 021    [o]
    - - 025      (seq? o) + + 022    (and
    - - 026      (when + + 023      (map? o)
    - - 027        (and + + 024      (vertex? (:start o))
    - - 028          (vertex? (first o)) + + 025      (vertex? (:end o))))
    - - 029          (vertex? (first (rest o)))) + + 026  
    - - 030        (cons + + 027  (defn length
    - - 031          {:start (first o) + + 028    "Return the length of the edge `e`."
    - - 032           :end (first (rest o))} + + 029    [e]
    - - 033          (path->edges (rest o)))) + + 030    (let [start (ensure3d (:start e))
    - - 034      (path? o) + + 031          end (ensure3d (:end e))]
    - - 035      (path->edges (:nodes o)) + + 032      (m/sqrt
    - - 036      (polygon? o) + + 033        (reduce
    - - 037      (path->edges (polygon->path o)))) + + 034          + +
    + + 035          (map +
    + + 036            #(m/expt (- (% end) (% start)) 2) +
    + + 037            [:x :y :z])))))
    038  
    - 039  (defn length + 039  (defn unit-vector
    - 040    "Return the length of the edge `e`." + 040    "Return an vertex parallel to `e` starting from the coordinate origin. Two
    - 041    [e] -
    - - 042    (let [start (ensure3d (:start e)) -
    - - 043          end (ensure3d (:end e))] -
    - - 044      (m/sqrt -
    - - 045        (reduce -
    - - 046          + -
    - - 047          (map -
    - - 048            #(m/expt (- (% end) (% start)) 2) -
    - - 049            [:x :y :z]))))) -
    - - 050   -
    - - 051  (defn unit-vector + 041    edges which are parallel will have the same unit vector."
    - 052    "Return an vertex parallel to `e` starting from the coordinate origin. Two -
    - - 053    edges which are parallel will have the same unit vector." -
    - - 054    [e] + 042    [e]
    - 055    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} + 043    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))}
    - 056          l (length e')] + 044          l (length e')]
    - 057      (reduce + 045      (reduce
    - 058        merge + 046        merge
    - 059        {} + 047        {}
    - 060        (map + 048        (map
    - 061          (fn [k] + 049          (fn [k]
    - 062            {k (/ (- (k (:end e')) (k (:start e'))) l)}) + 050            {k (/ (- (k (:end e')) (k (:start e'))) l)})
    - 063          [:x :y :z])))) + 051          [:x :y :z]))))
    - 064   + 052  
    - 065  (defn parallel? + 053  (defn parallel?
    - 066    "True if all `edges` passed are parallel with one another." + 054    "True if all `edges` passed are parallel with one another."
    - 067    ;; TODO: this bears being wary about, dealing with floating point arithmetic. + 055    ;; TODO: this bears being wary about, dealing with floating point arithmetic.
    - 068    ;; Keep an eye out for spurious errors. + 056    ;; Keep an eye out for spurious errors.
    - 069    [& edges] + 057    [& edges]
    - 070    (let [uvs (map unit-vector edges)] + 058    (let [uvs (map unit-vector edges)]
    - 071      (every? + 059      (every?
    - 072        #(= % (first uvs)) + 060        #(= % (first uvs))
    - 073        (rest uvs)))) + 061        (rest uvs))))
    - 074   + 062  
    - 075  (defn collinear? + 063  (defn collinear?
    - 076    "True if edges `e1` and `e2` are collinear with one another." + 064    "True if edges `e1` and `e2` are collinear with one another."
    - 077    [e1 e2] + 065    [e1 e2]
    - 078    (parallel? + 066    (parallel?
    - 079      e1 + 067      e1
    - 080      e2 + 068      e2
    - 081      {:start (:start e1) :end (:start e2)})) + 069      {:start (:start e1) :end (:start e2)}))
    - 082   + 070  
    diff --git a/docs/cloverage/walkmap/path.clj.html b/docs/cloverage/walkmap/path.clj.html index 7e8c4d2..247af41 100644 --- a/docs/cloverage/walkmap/path.clj.html +++ b/docs/cloverage/walkmap/path.clj.html @@ -8,115 +8,229 @@ 001  (ns walkmap.path

    - 002    "Essentially the specification for things we shall consider to be path." + 002    "Essentially the specification for things we shall consider to be path.
    - 003    (:require [walkmap.polygon :refer [polygon?]] + 003    **Note that** for these purposes `path` means any continuous linear
    - 004              [walkmap.vertex :refer [vertex?]])) + 004    feature, where such features specifically include watercourses." +
    + + 005    (:require [walkmap.edge :as e] +
    + + 006              [walkmap.polygon :refer [polygon?]] +
    + + 007              [walkmap.vertex :refer [vertex?]]))
    - 005   + 008  
    - 006  (defn path? + 009  (defn path?
    - 007    "True if `o` satisfies the conditions for a path. A path shall be a map + 010    "True if `o` satisfies the conditions for a path. A path shall be a map
    - 008    having the key `:nodes`, whose value shall be a sequence of vertices as + 011    having the key `:nodes`, whose value shall be a sequence of vertices as
    - 009    defined in `walkmap.vertex`." + 012    defined in `walkmap.vertex`."
    - 010    [o] + 013    [o]
    - 011    (let + 014    (let
    - 012      [v (:nodes o)] + 015      [v (:nodes o)]
    - - 013      (and + + 016      (and
    - 014        (seq? v) + 017        (seq? v)
    - 015        (> (count v) 2) + 018        (> (count v) 2)
    - 016        (every? vertex? v) + 019        (every? vertex? v) +
    + + 020        (:id o)
    - 017        (or (nil? (:kind o)) (= (:kind o) :path))))) + 021        (or (nil? (:kind o)) (= (:kind o) :path)))))
    - 018   + 022  
    - 019  (defn make-path + 023  (defn path
    - 020    [nodes] + 024    "Return a path constructed from these `vertices`." +
    + + 025    [& vertices]
    - 021    (if + 026    (if
    - 022      (every? vertex? nodes) + 027      (every? vertex? vertices)
    - 023      {:nodes nodes :id (keyword (gensym "path")) :kind :path} + 028      {:nodes vertices :id (keyword (gensym "path")) :kind :path}
    - 024      (throw (Exception. "Each item on path must be a vertex.")))) + 029      (throw (IllegalArgumentException. "Each item on path must be a vertex."))))
    - 025   + 030  
    - 026  (defn polygon->path + 031  (defn polygon->path
    - 027    "If `o` is a polygon, return an equivalent path. What's different about + 032    "If `o` is a polygon, return an equivalent path. What's different about
    - 028    a path is that in polygons there is an implicit edge between the first + 033    a path is that in polygons there is an implicit edge between the first
    - 029    vertex and the last. In paths, there isn't, so we need to add that + 034    vertex and the last. In paths, there isn't, so we need to add that
    - 030    edge explicitly. + 035    edge explicitly.
    - 031   + 036  
    - 032    If `o` is not a polygon, will throw an exception." + 037    If `o` is not a polygon, will throw an exception."
    - 033    [o] + 038    [o]
    - 034    (if + 039    (if
    - 035      (polygon? o) + 040      (polygon? o)
    - 036      (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) + 041      (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o)))))
    - 037      (throw (Exception. "Not a polygon!")))) + 042      (throw (IllegalArgumentException. "Not a polygon!"))))
    - 038   + 043   +
    + + 044  (defn path->edges +
    + + 045    "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of +
    + + 046    edges representing that path, polygon or sequence. +
    + + 047   +
    + + 048    Throws `IllegalArgumentException` if `o` is not a path, a polygon, or +
    + + 049    sequence of vertices." +
    + + 050    [o] +
    + + 051    (cond +
    + + 052      (seq? o) +
    + + 053      (when +
    + + 054        (and +
    + + 055          (vertex? (first o)) +
    + + 056          (vertex? (first (rest o)))) +
    + + 057        (cons +
    + + 058          (e/edge (first o) (rest o)) +
    + + 059          (path->edges (rest o)))) +
    + + 060      (path? o) +
    + + 061      (path->edges (:nodes o)) +
    + + 062      :else +
    + + 063      (throw (IllegalArgumentException. +
    + + 064               "Not a path or sequence of vertices!")))) +
    + + 065   +
    + + 066  (defn length +
    + + 067    "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 +
    + + 069    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*, +
    + + 071    since paths will generally be rendered as spline curves." +
    + + 072    [path] +
    + + 073    (if +
    + + 074      (path? path) +
    + + 075      (reduce + (map e/length (path->edges path))) +
    + + 076      (throw (IllegalArgumentException. "Not a path!"))))
    diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html index e6cd3a5..61b4549 100644 --- a/docs/cloverage/walkmap/stl.clj.html +++ b/docs/cloverage/walkmap/stl.clj.html @@ -29,7 +29,7 @@ 008              [walkmap.polygon :refer [polygon?]]

    - 009              [walkmap.vertex :refer [canonicalise-vertex]]) + 009              [walkmap.vertex :as v])
    010    (:import org.clojars.smee.binary.core.BinaryIO @@ -212,7 +212,7 @@ 069      ;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?`
    - 070      (:x o) (canonicalise-vertex o) + 070      (:x o) (v/canonicalise o)
    071      ;; shouldn't happen @@ -244,10 +244,10 @@ 080    [filename]
    - + 081    (let [in (io/input-stream filename)]
    - + 082      (canonicalise (b/decode binary-stl in))))
    diff --git a/docs/cloverage/walkmap/superstructure.clj.html b/docs/cloverage/walkmap/superstructure.clj.html index fe56429..3c16e1c 100644 --- a/docs/cloverage/walkmap/superstructure.clj.html +++ b/docs/cloverage/walkmap/superstructure.clj.html @@ -70,19 +70,19 @@ 022    [s o v]
    - + 023    (if-not (v/vertex? o)
    - + 024      (if (:id o)
    - + 025        (if (v/vertex? v)
    - + 026          (let [vi (or (:vertex-index s) {})
    - + 027                current (or (vi (:id v)) {})]
    @@ -91,7 +91,7 @@ 029            ;; stage we need to build a map.
    - + 030            (assoc vi (:id v) (assoc current (:id o) (:id v))))
    @@ -136,28 +136,28 @@ 044    [s o]
    - + 045    (assoc
    - + 046      s
    047      :vertex-index
    - + 048      (reduce
    - + 049        u/deep-merge
    - + 050        (map
    - + 051          #(index-vertex s o %)
    - + 052          (u/vertices o)))))
    @@ -196,22 +196,22 @@ 064    ([o]
    - + 065     (add-to-superstructure {} o))
    066    ([s o]
    - + 067    (cond
    - + 068      (map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))]
    - + 069                 (index-vertices (assoc s (:id o') o') o'))
    - + 070      (coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o))
    @@ -226,11 +226,5 @@ 074  
    - - 075  (:vertex-index (add-to-superstructure (:facets (s/decode-binary-stl "resources/isle_of_man.stl")))) -
    - - 076  (s/decode-binary-stl "resources/isle_of_man.stl") -
    diff --git a/docs/cloverage/walkmap/tag.clj.html b/docs/cloverage/walkmap/tag.clj.html new file mode 100644 index 0000000..53bb926 --- /dev/null +++ b/docs/cloverage/walkmap/tag.clj.html @@ -0,0 +1,203 @@ + + + + walkmap/tag.clj + + + + 001  (ns walkmap.tag +
    + + 002    "Code for tagging, untagging, and finding tags on objects. Note the use of +
    + + 003    the namespaced keyword, `:walkmap.tag/tags`, denoted in this file `::tags`. +
    + + 004    This is in an attempt to avoid name clashes with other uses of this key." +
    + + 005    (:require [clojure.set :refer [difference union]])) +
    + + 006   +
    + + 007  (defn tagged? +
    + + 008    "True if this `object` is tagged with each of these `tags`. It is an error +
    + + 009    (and an exception will be thrown) if +
    + + 010   +
    + + 011    1. `object` is not a map; +
    + + 012    2. any of `tags` is not a keyword." +
    + + 013    [object & tags] +
    + + 014    (if +
    + + 015      (map? object) +
    + + 016      (if +
    + + 017        (every? keyword? tags) +
    + + 018        (let [ot (::tags object)] +
    + + 019          (and +
    + + 020            (set? ot) +
    + + 021            (every? ot tags))) +
    + + 022        (throw (IllegalArgumentException. +
    + + 023                 (str "Must be keyword(s): " (map type tags))))) +
    + + 024      (throw (IllegalArgumentException. +
    + + 025               (str "Must be a map: " (type object)))))) +
    + + 026   +
    + + 027  (defn tag +
    + + 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 +
    + + 030    thrown) if +
    + + 031   +
    + + 032    1. `object` is not a map; +
    + + 033    2. any of `tags` is not a keyword." +
    + + 034    [object & tags] +
    + + 035    (if +
    + + 036      (map? object) +
    + + 037      (if +
    + + 038        (every? keyword? tags) +
    + + 039        (assoc object ::tags (union (set tags) (::tags object))) +
    + + 040        (throw (IllegalArgumentException. +
    + + 041                 (str "Must be keyword(s): " (map type tags))))) +
    + + 042      (throw (IllegalArgumentException. +
    + + 043               (str "Must be a map: " (type object)))))) +
    + + 044   +
    + + 045  (defmacro tags +
    + + 046    "Return the tags of this object, if any." +
    + + 047    [object] +
    + + 048    `(::tags ~object)) +
    + + 049   +
    + + 050  (defn untag +
    + + 051    "Return an object like this `object` but with these `tags` removed from its +
    + + 052    tags, if present. It is an error (and an exception will be thrown) if +
    + + 053   +
    + + 054    1. `object` is not a map; +
    + + 055    2. any of `tags` is not a keyword." +
    + + 056    [object & tags] +
    + + 057    (if +
    + + 058      (map? object) +
    + + 059      (if +
    + + 060        (every? keyword? tags) +
    + + 061        (assoc object ::tags (difference (::tags object) (set tags))) +
    + + 062        (throw (IllegalArgumentException. +
    + + 063                 (str "Must be keywords: " (map type tags))))) +
    + + 064      (throw (IllegalArgumentException. +
    + + 065               (str "Must be a map: " (type object)))))) +
    + + diff --git a/docs/cloverage/walkmap/utils.clj.html b/docs/cloverage/walkmap/utils.clj.html index f0d7116..44beefb 100644 --- a/docs/cloverage/walkmap/utils.clj.html +++ b/docs/cloverage/walkmap/utils.clj.html @@ -34,10 +34,10 @@ 010    [& vals]
    - + 011    (if (every? map? vals)
    - + 012      (apply merge-with deep-merge vals)
    @@ -70,13 +70,13 @@ 022    [o]
    - + 023    (cond
    - + 024      (v/vertex? o) (list o)
    - + 025      (q/polygon? o) (:vertices o)
    diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html index 3ba073f..c5c0170 100644 --- a/docs/cloverage/walkmap/vertex.clj.html +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -8,238 +8,247 @@ 001  (ns walkmap.vertex
    - 002    "Essentially the specification for things we shall consider to be vertices.") + 002    "Essentially the specification for things we shall consider to be vertices.
    003  
    + + 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`.") +
    + + 006   +
    - 004  (defn vertex-key + 007  (defn vertex-key
    - 005    "Making sure we get the same key everytime we key a vertex with the same + 008    "Making sure we get the same key everytime we key a vertex with the same
    - 006    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally + 009    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
    - 007    `:z`." + 010    `:z`."
    - 008    [o] + 011    [o]
    - 009    (cond + 012    (cond
    - 010      (and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}")) + 013      (and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}"))
    - 011      (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}")) + 014      (and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}"))
    - 012      :else (throw (IllegalArgumentException. "Not a vertex.")))) + 015      :else (throw (IllegalArgumentException. "Not a vertex."))))
    - 013   + 016  
    - 014  (defn vertex? + 017  (defn vertex?
    - 015    "True if `o` satisfies the conditions for a vertex. That is, essentially, + 018    "True if `o` satisfies the conditions for a vertex. That is, essentially,
    - 016    that it must rerpresent a two- or three- dimensional vector. A vertex is + 019    that it must rerpresent a two- or three- dimensional vector. A vertex is
    - 017    shall be a map having at least the keys `:x` and `:y`, where the value of + 020    shall be a map having at least the keys `:x` and `:y`, where the value of
    - 018    those keys is a number. If the key `:z` is also present, its value must also + 021    those keys is a number. If the key `:z` is also present, its value must also
    - 019    be a number. + 022    be a number.
    - 020   + 023  
    - 021    The name  `vector?` was not used as that would clash with a function of that + 024    The name  `vector?` was not used as that would clash with a function of that
    - 022    name in `clojure.core` whose semantics are entirely different." + 025    name in `clojure.core` whose semantics are entirely different."
    - 023    [o] + 026    [o]
    - 024    (and + 027    (and
    - 025      (map? o) + 028      (map? o)
    - 026      (:id o) + 029      (:id o)
    - 027      (number? (:x o)) + 030      (number? (:x o))
    - 028      (number? (:y o)) + 031      (number? (:y o))
    - - 029      (or (nil? (:z o)) (number? (:z o))) + + 032      (or (nil? (:z o)) (number? (:z o)))
    - 030      (or (nil? (:kind o)) (= (:kind o) :vertex)))) + 033      (or (nil? (:kind o)) (= (:kind o) :vertex))))
    - 031   + 034  
    - 032  (defn make-vertex + 035  (defn vertex
    - 033    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map + 036    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
    - 034    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`. + 037    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`.
    - 035    It's not necessary to use this function to create a vertex, but the `:id` + 038    It's not necessary to use this function to create a vertex, but the `:id`
    - 036    must be present and must be unique." + 039    must be present and must be unique."
    - 037    ([x y] + 040    ([x y]
    - 038     (let [v {:x x :y y :kind :vertex}] + 041     (let [v {:x x :y y :kind :vertex}]
    - 039       (assoc v :id (vertex-key v)))) + 042       (assoc v :id (vertex-key v))))
    - 040    ([x y z] + 043    ([x y z]
    - 041     (assoc (make-vertex x y) :z z))) + 044     (assoc (vertex x y) :z z)))
    - 042   + 045  
    - 043  (defn canonicalise-vertex + 046  (defn canonicalise
    - 044    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, + 047    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
    - 045    upgrade it to something we will recognise as a vertex." + 048    upgrade it to something we will recognise as a vertex."
    - 046    [o] + 049    [o]
    - 047    (if + 050    (if
    - 048      (and + 051      (and
    - 049        (map? o) + 052        (map? o)
    - 050        (number? (:x o)) + 053        (number? (:x o))
    - 051        (number? (:y o)) + 054        (number? (:y o))
    - 052        (or (nil? (:z o)) (number? (:z o)))) + 055        (or (nil? (:z o)) (number? (:z o))))
    - 053      (assoc o :kind :vertex :id (vertex-key o)) + 056      (assoc o :kind :vertex :id (vertex-key o))
    - 054      (throw (IllegalArgumentException. "Not a vertex.")))) + 057      (throw (IllegalArgumentException. "Not a proto-vertex: must have numeric `:x` and `:y`."))))
    - 055   + 058  
    - 056  (def ensure3d + 059  (def ensure3d
    - 057    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise + 060    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
    - 058    return a vertex like `o` but having thie `dflt` value as the value of its + 061    return a vertex like `o` but having thie `dflt` value as the value of its
    - 059    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified. + 062    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified.
    - 060   + 063  
    - 061    If `o` is not a vertex, throws an exception." + 064    If `o` is not a vertex, throws an exception."
    - 062    (memoize + 065    (memoize
    - 063      (fn + 066      (fn
    - 064        ([o] + 067        ([o]
    - 065         (ensure3d o 0.0)) + 068         (ensure3d o 0.0))
    - 066        ([o dflt] + 069        ([o dflt]
    - 067         (cond + 070         (cond
    - 068           (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!")) + 071           (not (vertex? o)) (throw (IllegalArgumentException. "Not a vertex!"))
    - 069           (:z o) o + 072           (:z o) o
    - 070           :else (assoc o :z dflt)))))) + 073           :else (assoc o :z dflt))))))
    - 071   + 074  
    - 072  (def ensure2d + 075  (def ensure2d
    - 073    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." + 076    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
    - 074    (memoize + 077    (memoize
    - 075      (fn [o] + 078      (fn [o]
    - 076        (if + 079        (if
    - 077          (vertex? o) + 080          (vertex? o)
    - 078          (assoc o :z 0.0) + 081          (assoc o :z 0.0)
    - 079          (throw (IllegalArgumentException. "Not a vertex!")))))) + 082          (throw (IllegalArgumentException. "Not a vertex!"))))))
    diff --git a/docs/codox/dali-performance.html b/docs/codox/dali-performance.html index f90fcc1..6d81232 100644 --- a/docs/codox/dali-performance.html +++ b/docs/codox/dali-performance.html @@ -1,6 +1,6 @@ -Dali performance

    Dali performance

    +Dali performance

    Dali performance

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

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

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

    diff --git a/docs/codox/index.html b/docs/codox/index.html index 2fd020f..766d5d5 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.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.utils

      Miscellaneous utility functions.

      Public variables and functions:

      walkmap.vertex

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

      Public variables and functions:

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

      Walkmap 0.1.0-SNAPSHOT

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

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

      Installation

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

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

      Topics

      Namespaces

      walkmap.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 diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 8e5f50e..b999120 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,6 +1,6 @@ -Introduction to walkmap

          Introduction to walkmap

          +Introduction to walkmap

          Introduction to walkmap

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

          Usage

          What works:

          diff --git a/docs/codox/walkmap.core.html b/docs/codox/walkmap.core.html index 4dbd673..73868ed 100644 --- a/docs/codox/walkmap.core.html +++ b/docs/codox/walkmap.core.html @@ -1,3 +1,3 @@ -walkmap.core documentation

          walkmap.core

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

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

          walkmap.core

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

          \ No newline at end of file diff --git a/docs/codox/walkmap.edge.html b/docs/codox/walkmap.edge.html index fa1c0b8..53996c4 100644 --- a/docs/codox/walkmap.edge.html +++ b/docs/codox/walkmap.edge.html @@ -1,3 +1,4 @@ -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 path. A path 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.

          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.

          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 diff --git a/docs/codox/walkmap.geometry.html b/docs/codox/walkmap.geometry.html index bd7067c..722174d 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

          on?

          (on? e v)

          True if the vertex v is on the edge e.

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

          walkmap.ocean

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

          *sea-level*

          dynamic

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

          +walkmap.ocean documentation

          walkmap.ocean

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

          *sea-level*

          dynamic

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

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

          cull-ocean-facets

          (cull-ocean-facets stl)

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

          ocean?

          (ocean? facet)

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

          \ No newline at end of file diff --git a/docs/codox/walkmap.path.html b/docs/codox/walkmap.path.html index 0a63179..eadb965 100644 --- a/docs/codox/walkmap.path.html +++ b/docs/codox/walkmap.path.html @@ -1,4 +1,4 @@ -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.

          +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 diff --git a/docs/codox/walkmap.polygon.html b/docs/codox/walkmap.polygon.html index c3c2657..997efc6 100644 --- a/docs/codox/walkmap.polygon.html +++ b/docs/codox/walkmap.polygon.html @@ -1,3 +1,3 @@ -walkmap.polygon documentation

          walkmap.polygon

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

          polygon?

          (polygon? o)

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

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

          walkmap.polygon

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

          polygon?

          (polygon? o)

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

          \ No newline at end of file diff --git a/docs/codox/walkmap.stl.html b/docs/codox/walkmap.stl.html index 271e681..bac014e 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.

          +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 diff --git a/docs/codox/walkmap.superstructure.html b/docs/codox/walkmap.superstructure.html index 0dbecd5..0876fb7 100644 --- a/docs/codox/walkmap.superstructure.html +++ b/docs/codox/walkmap.superstructure.html @@ -1,6 +1,6 @@ -walkmap.superstructure documentation

          walkmap.superstructure

          single indexing structure for walkmap objects

          add-to-superstructure

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

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

          +walkmap.superstructure documentation

          walkmap.superstructure

          single indexing structure for walkmap objects

          add-to-superstructure

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

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

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

          1. s is not a map;
          2. diff --git a/docs/codox/walkmap.svg.html b/docs/codox/walkmap.svg.html index 2f47187..6b8baa2 100644 --- a/docs/codox/walkmap.svg.html +++ b/docs/codox/walkmap.svg.html @@ -1,3 +1,3 @@ -walkmap.svg documentation

            walkmap.svg

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

            *preferred-svg-render*

            dynamic

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

            binary-stl-file->svg

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

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

            dali-stl->svg

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

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

            hiccup-stl->svg

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

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

            stl->svg

            (stl->svg stl)

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

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

            walkmap.svg

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

            *preferred-svg-render*

            dynamic

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

            binary-stl-file->svg

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

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

            dali-stl->svg

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

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

            hiccup-stl->svg

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

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

            stl->svg

            (stl->svg stl)

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

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

            walkmap.tag

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

            tag

            (tag object & tags)

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

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

            tagged?

            (tagged? object & tags)

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

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

            tags

            macro

            (tags object)

            Return the tags of this object, if any.

            untag

            (untag object & tags)

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

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

            walkmap.utils

            Miscellaneous utility functions.

            deep-merge

            (deep-merge & vals)

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

            vertices

            (vertices o)

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

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

            walkmap.utils

            Miscellaneous utility functions.

            deep-merge

            (deep-merge & vals)

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

            vertices

            (vertices o)

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

            \ No newline at end of file diff --git a/docs/codox/walkmap.vertex.html b/docs/codox/walkmap.vertex.html index a8e5c6e..77c0205 100644 --- a/docs/codox/walkmap.vertex.html +++ b/docs/codox/walkmap.vertex.html @@ -1,5 +1,5 @@ -walkmap.vertex documentation

            walkmap.vertex

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

            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.

            +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 diff --git a/src/walkmap/edge.clj b/src/walkmap/edge.clj index 356414b..17319ed 100644 --- a/src/walkmap/edge.clj +++ b/src/walkmap/edge.clj @@ -3,10 +3,17 @@ An edge is a line segment having just a start and an end, with no intervening nodes." (:require [clojure.math.numeric-tower :as m] - [walkmap.path :refer [path? polygon->path]] [walkmap.polygon :refer [polygon?]] [walkmap.vertex :refer [ensure3d vertex?]])) +(defn edge + "Return an edge between vertices `v1` and `v2`." + [v1 v2] + (if + (and (vertex? v1) (vertex? v2)) + {:kind :edge :id (keyword (gensym "edge")) :start v1 :end v2} + (throw (IllegalArgumentException. "Must be vertices.")))) + (defn edge? "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 @@ -17,31 +24,6 @@ (vertex? (:start o)) (vertex? (:end o)))) -(defn path->edges - "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." - [o] - (cond - (seq? o) - (when - (and - (vertex? (first o)) - (vertex? (first (rest o)))) - (cons - {:start (first o) - :end (first (rest o))} - (path->edges (rest o)))) - (path? o) - (path->edges (:nodes o)) - (polygon? o) - (path->edges (polygon->path o)) - :else - (throw (IllegalArgumentException. - "Not a path, polygon, or sequence of vertices!")))) - (defn length "Return the length of the edge `e`." [e] diff --git a/src/walkmap/path.clj b/src/walkmap/path.clj index 1928d6e..ebd47c3 100644 --- a/src/walkmap/path.clj +++ b/src/walkmap/path.clj @@ -1,6 +1,9 @@ (ns walkmap.path - "Essentially the specification for things we shall consider to be path." - (:require [walkmap.polygon :refer [polygon?]] + "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." + (:require [walkmap.edge :as e] + [walkmap.polygon :refer [polygon?]] [walkmap.vertex :refer [vertex?]])) (defn path? @@ -14,14 +17,16 @@ (seq? v) (> (count v) 2) (every? vertex? v) + (:id o) (or (nil? (:kind o)) (= (:kind o) :path))))) -(defn make-path - [nodes] +(defn path + "Return a path constructed from these `vertices`." + [& vertices] (if - (every? vertex? nodes) - {:nodes nodes :id (keyword (gensym "path")) :kind :path} - (throw (Exception. "Each item on path must be a vertex.")))) + (every? vertex? vertices) + {:nodes vertices :id (keyword (gensym "path")) :kind :path} + (throw (IllegalArgumentException. "Each item on path must be a vertex.")))) (defn polygon->path "If `o` is a polygon, return an equivalent path. What's different about @@ -34,5 +39,40 @@ (if (polygon? o) (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o))))) - (throw (Exception. "Not a polygon!")))) + (throw (IllegalArgumentException. "Not a polygon!")))) +(defn path->edges + "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." + [o] + (cond + (seq? o) + (when + (and + (vertex? (first o)) + (vertex? (first (rest o)))) + (cons + ;; TODO: think about: when constructing an edge from a path, should the + ;; constructed edge be tagged with the tags of the path? + (e/edge (first o) (rest o)) + (path->edges (rest o)))) + (path? o) + (path->edges (:nodes o)) + :else + (throw (IllegalArgumentException. + "Not a path or sequence of vertices!")))) + +(defn length + "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] + (if + (path? path) + (reduce + (map e/length (path->edges path))) + (throw (IllegalArgumentException. "Not a path!")))) diff --git a/src/walkmap/stl.clj b/src/walkmap/stl.clj index accbb09..123bb49 100644 --- a/src/walkmap/stl.clj +++ b/src/walkmap/stl.clj @@ -6,7 +6,7 @@ [org.clojars.smee.binary.core :as b] [taoensso.timbre :as l :refer [info error spy]] [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :refer [canonicalise-vertex]]) + [walkmap.vertex :as v]) (:import org.clojars.smee.binary.core.BinaryIO java.io.DataInput)) @@ -67,7 +67,7 @@ :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) (canonicalise-vertex o) + (:x o) (v/canonicalise o) ;; shouldn't happen :else o)) diff --git a/src/walkmap/tag.clj b/src/walkmap/tag.clj index aacab67..5502fff 100644 --- a/src/walkmap/tag.clj +++ b/src/walkmap/tag.clj @@ -5,7 +5,11 @@ (:require [clojure.set :refer [difference union]])) (defn tagged? - "True if this `object` is tagged with each of these `tags`." + "True if this `object` is tagged with each of these `tags`. It is an error + (and an exception will be thrown) if + + 1. `object` is not a map; + 2. any of `tags` is not a keyword." [object & tags] (if (map? object) @@ -14,8 +18,7 @@ (let [ot (::tags object)] (and (set? ot) - (every? ot tags) - true)) + (every? ot tags))) (throw (IllegalArgumentException. (str "Must be keyword(s): " (map type tags))))) (throw (IllegalArgumentException. @@ -23,7 +26,11 @@ (defn tag "Return an object like this `object` but with these `tags` added to its tags, - if they are not already present." + 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." [object & tags] (if (map? object) @@ -35,9 +42,17 @@ (throw (IllegalArgumentException. (str "Must be a map: " (type object)))))) +(defmacro tags + "Return the tags of this object, if any." + [object] + `(::tags ~object)) + (defn untag "Return an object like this `object` but with these `tags` removed from its - tags, if present." + tags, if 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." [object & tags] (if (map? object) diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index 6a01de2..ea8d8bc 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -1,5 +1,8 @@ (ns walkmap.vertex - "Essentially the specification for things we shall consider to be vertices.") + "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`.") (defn vertex-key "Making sure we get the same key everytime we key a vertex with the same @@ -29,7 +32,7 @@ (or (nil? (:z o)) (number? (:z o))) (or (nil? (:kind o)) (= (:kind o) :vertex)))) -(defn make-vertex +(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`. It's not necessary to use this function to create a vertex, but the `:id` @@ -38,9 +41,9 @@ (let [v {:x x :y y :kind :vertex}] (assoc v :id (vertex-key v)))) ([x y z] - (assoc (make-vertex x y) :z z))) + (assoc (vertex x y) :z z))) -(defn canonicalise-vertex +(defn canonicalise "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`, upgrade it to something we will recognise as a vertex." [o] @@ -51,7 +54,7 @@ (number? (:y o)) (or (nil? (:z o)) (number? (:z o)))) (assoc o :kind :vertex :id (vertex-key o)) - (throw (IllegalArgumentException. "Not a vertex.")))) + (throw (IllegalArgumentException. "Not a proto-vertex: must have numeric `:x` and `:y`.")))) (def ensure3d "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise diff --git a/test/walkmap/edge_test.clj b/test/walkmap/edge_test.clj index fe5865b..e8dc303 100644 --- a/test/walkmap/edge_test.clj +++ b/test/walkmap/edge_test.clj @@ -1,12 +1,12 @@ (ns walkmap.edge-test (:require [clojure.test :refer :all] [walkmap.edge :refer :all] - [walkmap.vertex :refer [make-vertex]])) + [walkmap.vertex :refer [vertex]])) (deftest edge-test (testing "identification of edges." - (is (edge? {:start (make-vertex 0.0 0.0 0.0) - :end (make-vertex 3 4 0.0)}) "It is.") + (is (edge? {:start (vertex 0.0 0.0 0.0) + :end (vertex 3 4 0.0)}) "It is.") (is (not (edge? {:start {:y 0.0 :z 0.0 :id 'foo} :end {:x 3 :y 4 :z 0.0 :id 'bar}})) "Start lacks :x key") (is (not (edge? {:start {:x nil :y 0.0 :z 0.0 :id 'foo} @@ -17,7 +17,16 @@ :finish {:x 3 :y 4 :z 0.0 :id 'bar}})) "Lacks end key") (is (not (edge? {:start {:x "zero" :y 0.0 :z 0.0 :id 'foo} :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 construction-test + (testing "Construction of edges." + (is (edge? (edge (vertex 1.0 2.0 3.0) (vertex 4.0 8.0 12.0))) + "If both arguments are vertices, we should get an edge") + (is (thrown? IllegalArgumentException (edge "Not a vertex" (vertex 1 2))) + "If first argument is not a vertex, we should get an exception.") + (is (thrown? IllegalArgumentException (edge (vertex 1 2) "Not a vertex")) + "If second argument is not a vertex, we should get an exception."))) (deftest length-test (testing "length of an edge" diff --git a/test/walkmap/tag_test.clj b/test/walkmap/tag_test.clj index 9f0a269..00d578a 100644 --- a/test/walkmap/tag_test.clj +++ b/test/walkmap/tag_test.clj @@ -5,7 +5,7 @@ (deftest tag-tests (testing "Tagging" (is (set? (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz))) - "The value of `:walkmap.tag/tags should be a set.") + "The value of `:walkmap.tag/tags` should be a set.") (is (= (count (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz))) 4) "All the tags passed should be added.") (is (:walkmap.tag/tags (tag {} :foo :bar :ban :froboz) :ban) @@ -17,12 +17,18 @@ "`tagged?` should return an explicit `true`, not any other value.") (is (tagged? (tag {} :foo :bar :ban :froboz) :bar :froboz) "We should be able to test for the presence of more than one tag") + (is (false? (tagged? {} :foo)) + "A missing `:walkmap.tag/tags` should not cause an error.") (is (= (tagged? (tag {} :foo :bar :ban :froboz) :bar :cornflakes) false) "If any of the queried tags is missing, false should be returned") (is (tagged? (tag (tag {} :foo) :bar) :foo :bar) "We should be able to add tags to an already tagged object") (is (false? (tagged? (tag {} :foo :bar) :cornflakes)) "`tagged?` should return an explicit `false` if a queried tag is missing.") + (is (= (tags (tag {} :foo)) #{:foo}) + "`tags` should return the tags on the object, if any.") + (is (every? nil? (map #(tags %) [1 :one "one" [:one] {:one 1}])) + "Things which don't have tags don't have tags, and that's not a problem.") (let [object (tag {} :foo :bar :ban :froboz)] (is (= (untag object :cornflakes) object) "Removing a missing tag should have no effect.") From 1ab35dbe7dd6a3e37b50b49347d5f59ec3e26f98 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 27 May 2020 18:54:36 +0100 Subject: [PATCH 5/5] Line intersection looking good. --- docs/cloverage/index.html | 114 +-- docs/cloverage/walkmap/edge.clj.html | 499 +++++++++++-- docs/cloverage/walkmap/geometry.clj.html | 71 +- docs/cloverage/walkmap/path.clj.html | 52 +- docs/cloverage/walkmap/polygon.clj.html | 13 +- docs/cloverage/walkmap/stl.clj.html | 683 +++++++++++------- .../cloverage/walkmap/superstructure.clj.html | 179 +++-- docs/cloverage/walkmap/tag.clj.html | 2 +- docs/cloverage/walkmap/utils.clj.html | 59 +- docs/cloverage/walkmap/vertex.clj.html | 408 +++++++---- docs/codox/index.html | 2 +- docs/codox/walkmap.edge.html | 10 +- docs/codox/walkmap.geometry.html | 2 +- docs/codox/walkmap.path.html | 5 +- docs/codox/walkmap.stl.html | 6 +- docs/codox/walkmap.superstructure.html | 6 +- docs/codox/walkmap.tag.html | 2 +- docs/codox/walkmap.vertex.html | 8 +- src/walkmap/edge.clj | 129 +++- src/walkmap/geometry.clj | 35 +- src/walkmap/path.clj | 10 +- src/walkmap/polygon.clj | 1 + src/walkmap/stl.clj | 87 ++- src/walkmap/superstructure.clj | 21 +- src/walkmap/tag.clj | 2 +- src/walkmap/utils.clj | 11 +- src/walkmap/vertex.clj | 52 +- test/walkmap/edge_test.clj | 95 ++- test/walkmap/geometry_test.clj | 14 + 29 files changed, 1725 insertions(+), 853 deletions(-) create mode 100644 test/walkmap/geometry_test.clj 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.")))