From f616992191e17b4e93498421ed1681dac575184d Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Mon, 25 May 2020 23:55:52 +0100 Subject: [PATCH] #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`."))))