diff --git a/docs/cloverage/coverage.css b/docs/cloverage/coverage.css new file mode 100644 index 0000000..2be4e57 --- /dev/null +++ b/docs/cloverage/coverage.css @@ -0,0 +1,40 @@ +.covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: #558B55; +} + +.not-covered { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: red; +} + +.partial { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; + background-color: orange; +} + +.not-tracked { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +.blank { + font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace; +} + +td { + padding-right: 10px; +} + +td.with-bar { + width: 250px; + text-align: center; +} + +td.with-number { + text-align: right; +} + +td.ns-name { + min-width: 150px; + padding-right: 25px; +} diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html new file mode 100644 index 0000000..adaca25 --- /dev/null +++ b/docs/cloverage/index.html @@ -0,0 +1,149 @@ + + + + + Coverage Summary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Namespace Forms Forms % Lines Lines %TotalBlankInstrumented
walkmap.core
6
43
12.24 %
5
8
38.46 %44613
walkmap.edge
136
60
69.39 %
35
1
14
72.00 %82750
walkmap.geometry
2
121
1.63 %
2
11
15.38 %24313
walkmap.path
3
51
5.56 %
3
10
23.08 %30413
walkmap.polygon
2
25
7.41 %
2
6
25.00 %1738
walkmap.stl
46
221
17.23 %
18
46
28.13 %1261264
walkmap.svg
4
135
2.88 %
3
32
8.57 %50235
walkmap.vertex
62
23
72.94 %
14
3
5
77.27 %43522
Totals:27.77 %39.45 %
+ + diff --git a/docs/cloverage/walkmap/core.clj.html b/docs/cloverage/walkmap/core.clj.html new file mode 100644 index 0000000..58adc64 --- /dev/null +++ b/docs/cloverage/walkmap/core.clj.html @@ -0,0 +1,140 @@ + + + + walkmap/core.clj + + + + 001  (ns walkmap.core +
+ + 002    "At this stage, primarily utility functions dealing with stereolithography +
+ + 003    (STL) files. Not a stable API yet!" +
+ + 004    (:require [clojure.java.io :as io :refer [file output-stream input-stream]] +
+ + 005              [clojure.string :as s] +
+ + 006              [hiccup.core :refer [html]] +
+ + 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]])) +
+ + 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   +
+ + diff --git a/docs/cloverage/walkmap/edge.clj.html b/docs/cloverage/walkmap/edge.clj.html new file mode 100644 index 0000000..280afe2 --- /dev/null +++ b/docs/cloverage/walkmap/edge.clj.html @@ -0,0 +1,254 @@ + + + + walkmap/edge.clj + + + + 001  (ns walkmap.edge +
+ + 002    "Essentially the specification for things we shall consider to be an edge. +
+ + 003    An edge is a line segment having just a start and an end, with no intervening +
+ + 004    nodes." +
+ + 005    (:require [clojure.math.numeric-tower :as m] +
+ + 006              [walkmap.path :refer [path? polygon->path]] +
+ + 007              [walkmap.polygon :refer [polygon?]] +
+ + 008              [walkmap.vertex :refer [ensure3d vertex?]])) +
+ + 009   +
+ + 010  (defn edge? +
+ + 011    "True if `o` satisfies the conditions for a path. A path shall be a map +
+ + 012    having the keys `:start` and `:end`, such that the values of each of those +
+ + 013    keys shall be a vertex." +
+ + 014    [o] +
+ + 015    (and +
+ + 016      (map? o) +
+ + 017      (vertex? (:start o)) +
+ + 018      (vertex? (:end o)))) +
+ + 019   +
+ + 020  (defn path->edges +
+ + 021    "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of +
+ + 022    edges representing that path, polygon or sequence." +
+ + 023    [o] +
+ + 024    (cond +
+ + 025      (seq? o) +
+ + 026      (when +
+ + 027        (and +
+ + 028          (vertex? (first o)) +
+ + 029          (vertex? (first (rest o)))) +
+ + 030        (cons +
+ + 031          {:start (first o) +
+ + 032           :end (first (rest o))} +
+ + 033          (path->edges (rest o)))) +
+ + 034      (path? o) +
+ + 035      (path->edges (:nodes o)) +
+ + 036      (polygon? o) +
+ + 037      (path->edges (polygon->path o)))) +
+ + 038   +
+ + 039  (defn length +
+ + 040    "Return the length of the edge `e`." +
+ + 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 +
+ + 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] +
+ + 055    (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} +
+ + 056          l (length e')] +
+ + 057      (reduce +
+ + 058        merge +
+ + 059        {} +
+ + 060        (map +
+ + 061          (fn [k] +
+ + 062            {k (/ (- (k (:end e')) (k (:start e'))) l)}) +
+ + 063          [:x :y :z])))) +
+ + 064   +
+ + 065  (defn parallel? +
+ + 066    "True if all `edges` passed are parallel with one another." +
+ + 067    ;; TODO: this bears being wary about, dealing with floating point arithmetic. +
+ + 068    ;; Keep an eye out for spurious errors. +
+ + 069    [& edges] +
+ + 070    (let [uvs (map unit-vector edges)] +
+ + 071      (every? +
+ + 072        #(= % (first uvs)) +
+ + 073        (rest uvs)))) +
+ + 074   +
+ + 075  (defn collinear? +
+ + 076    "True if edges `e1` and `e2` are collinear with one another." +
+ + 077    [e1 e2] +
+ + 078    (parallel? +
+ + 079      e1 +
+ + 080      e2 +
+ + 081      {:start (:start e1) :end (:start e2)})) +
+ + 082   +
+ + diff --git a/docs/cloverage/walkmap/geometry.clj.html b/docs/cloverage/walkmap/geometry.clj.html new file mode 100644 index 0000000..c43eb85 --- /dev/null +++ b/docs/cloverage/walkmap/geometry.clj.html @@ -0,0 +1,80 @@ + + + + walkmap/geometry.clj + + + + 001  (ns walkmap.geometry +
+ + 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])) +
+ + 008   +
+ + 009  (defn on? +
+ + 010    "True if the vertex `v` is on the edge `e`." +
+ + 011    [e v] +
+ + 012    (let [p (v/ensure3d (:start e)) +
+ + 013          q (v/ensure3d v) +
+ + 014          r (v/ensure3d (:end e))] +
+ + 015      (and +
+ + 016        (e/collinear? p q r) +
+ + 017        (<= (:x q) (max (:x p) (:x r))) +
+ + 018        (>= (:x q) (min (:x p) (:x r))) +
+ + 019        (<= (:y q) (max (:y p) (:y r))) +
+ + 020        (>= (:y q) (min (:y p) (:y r))) +
+ + 021        (<= (:z q) (max (:z p) (:z r))) +
+ + 022        (>= (:z q) (min (:z p) (:z r)))))) +
+ + 023   +
+ + 024   +
+ + diff --git a/docs/cloverage/walkmap/path.clj.html b/docs/cloverage/walkmap/path.clj.html new file mode 100644 index 0000000..6b58a3d --- /dev/null +++ b/docs/cloverage/walkmap/path.clj.html @@ -0,0 +1,98 @@ + + + + walkmap/path.clj + + + + 001  (ns walkmap.path +
+ + 002    "Essentially the specification for things we shall consider to be path." +
+ + 003    (:require [walkmap.polygon :refer [polygon?]] +
+ + 004              [walkmap.vertex :refer [vertex?]])) +
+ + 005   +
+ + 006  (defn path? +
+ + 007    "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 +
+ + 009    defined in `walkmap.vertex`." +
+ + 010    [o] +
+ + 011    (let +
+ + 012      [v (:nodes o)] +
+ + 013      (and +
+ + 014        (seq? v) +
+ + 015        (> (count v) 2) +
+ + 016        (every? vertex? v)))) +
+ + 017   +
+ + 018  (defn polygon->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] +
+ + 026    (if +
+ + 027      (polygon? o) +
+ + 028      (assoc (dissoc o :vertices) :nodes (concat (:vertices o) (list (first (:vertices o))))) +
+ + 029      (throw (Exception. "Not a polygon!")))) +
+ + 030   +
+ + diff --git a/docs/cloverage/walkmap/polygon.clj.html b/docs/cloverage/walkmap/polygon.clj.html new file mode 100644 index 0000000..f8ebc8b --- /dev/null +++ b/docs/cloverage/walkmap/polygon.clj.html @@ -0,0 +1,59 @@ + + + + walkmap/polygon.clj + + + + 001  (ns walkmap.polygon +
+ + 002    "Essentially the specification for things we shall consider to be polygons." +
+ + 003    (:require [walkmap.vertex :refer [vertex?]])) +
+ + 004   +
+ + 005  (defn polygon? +
+ + 006    "True if `o` satisfies the conditions for a polygon. A polygon shall be a +
+ + 007    map which has a value for the key `:vertices`, where that value is a sequence +
+ + 008    of vertices." +
+ + 009    [o] +
+ + 010    (let +
+ + 011      [v (:vertices o)] +
+ + 012      (and +
+ + 013        (seq? v) +
+ + 014        (> (count v) 2) +
+ + 015        (every? vertex? v)))) +
+ + 016   +
+ + 017   +
+ + diff --git a/docs/cloverage/walkmap/stl.clj.html b/docs/cloverage/walkmap/stl.clj.html new file mode 100644 index 0000000..8e48e58 --- /dev/null +++ b/docs/cloverage/walkmap/stl.clj.html @@ -0,0 +1,386 @@ + + + + walkmap/stl.clj + + + + 001  (ns walkmap.stl +
+ + 002    "Utility functions dealing with stereolithography (STL) files. Not a stable API yet!" +
+ + 003    (:require [clojure.java.io :as io :refer [file output-stream input-stream]] +
+ + 004              [clojure.string :as s] +
+ + 005              [me.raynes.fs :as fs] +
+ + 006              [org.clojars.smee.binary.core :as b] +
+ + 007              [taoensso.timbre :as l :refer [info error spy]] +
+ + 008              [walkmap.polygon :refer [polygon?]]) +
+ + 009    (:import org.clojars.smee.binary.core.BinaryIO +
+ + 010             java.io.DataInput)) +
+ + 011   +
+ + 012  (defn stl? +
+ + 013    "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    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   +
+ + 018    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    ([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 +
+ + 042      :vertices [vect vect vect] +
+ + 043      :abc :ushort-le)) +
+ + 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 +
+ + 050     :facets (b/repeated facet))) +
+ + 051   +
+ + 052  (defn decode-binary-stl +
+ + 053    "Parse a binary STL file from this `filename` and return an STL structure +
+ + 054    representing its contents. +
+ + 055   +
+ + 056    **NOTE** that we've no way of verifying that the input file is binary STL +
+ + 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))) +
+ + 061   +
+ + 062  (defn- vect->str [prefix v] +
+ + 063    (str prefix " " (:x v) " " (:y v) " " (:z v) "\n")) +
+ + 064   +
+ + 065  (defn- facet2str [tri] +
+ + 066    (str +
+ + 067      (vect->str "facet normal" (:normal tri)) +
+ + 068      "outer loop\n" +
+ + 069      (apply str +
+ + 070             (map +
+ + 071               #(vect->str "vertex" %) +
+ + 072               (:vertices tri))) +
+ + 073      "endloop\nendfacet\n")) +
+ + 074   +
+ + 075  (defn stl->ascii +
+ + 076    "Return as a string an ASCII rendering of the `stl` structure." +
+ + 077    ([stl] +
+ + 078     (stl->ascii stl "unknown")) +
+ + 079    ([stl solidname] +
+ + 080     (str +
+ + 081       "solid " +
+ + 082       solidname +
+ + 083       (s/trim (:header stl)) +
+ + 084       "\n" +
+ + 085       (apply +
+ + 086         str +
+ + 087         (map +
+ + 088           facet2str +
+ + 089           (:facets stl))) +
+ + 090       "endsolid " +
+ + 091       solidname +
+ + 092       "\n"))) +
+ + 093   +
+ + 094  (defn write-ascii-stl +
+ + 095    "Write an `stl` structure as read by `decode-binary-stl` to this +
+ + 096    `filename` as ASCII encoded STL." +
+ + 097    ([filename stl] +
+ + 098     (let [b (fs/base-name filename true)] +
+ + 099       (write-ascii-stl +
+ + 100         filename stl +
+ + 101         (subs b 0 (or (s/index-of b ".") (count b)))))) +
+ + 102    ([filename stl solidname] +
+ + 103     (l/debug "Solid name is " solidname) +
+ + 104     (spit +
+ + 105       filename +
+ + 106       (stl->ascii stl solidname)))) +
+ + 107   +
+ + 108  (defn binary-stl-to-ascii +
+ + 109    "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 +
+ + 111    as `in-filename` but the extension `.ascii.stl`." +
+ + 112    ([in-filename] +
+ + 113     (let [[_ ext] (fs/split-ext in-filename)] +
+ + 114       (binary-stl-to-ascii +
+ + 115         in-filename +
+ + 116         (str +
+ + 117           (subs +
+ + 118             in-filename +
+ + 119             0 +
+ + 120             (or +
+ + 121               (s/last-index-of in-filename ".") +
+ + 122               (count in-filename))) +
+ + 123           ".ascii" +
+ + 124           ext)))) +
+ + 125    ([in-filename out-filename] +
+ + 126     (write-ascii-stl out-filename (decode-binary-stl in-filename)))) +
+ + diff --git a/docs/cloverage/walkmap/svg.clj.html b/docs/cloverage/walkmap/svg.clj.html new file mode 100644 index 0000000..d465adf --- /dev/null +++ b/docs/cloverage/walkmap/svg.clj.html @@ -0,0 +1,158 @@ + + + + walkmap/svg.clj + + + + 001  (ns walkmap.svg +
+ + 002    "Utility functions for writing stereolithography (STL) files (and possibly, +
+ + 003    later, other geometry files of interest to us) as scalable vector graphics +
+ + 004    (SVG)." +
+ + 005    (:require [clojure.string :as s] +
+ + 006              [taoensso.timbre :as l :refer [info error spy]] +
+ + 007              [walkmap.polygon :refer [polygon?]] +
+ + 008              [walkmap.vertex :refer [vertex?]])) +
+ + 009   +
+ + 010  (defn- facet->svg-poly +
+ + 011    [facet] +
+ + 012    [:polygon +
+ + 013     {:points (s/join " " (map #(str (:x %) "," (:y %)) (:vertices facet)))}]) +
+ + 014   +
+ + 015  (defn stl->svg +
+ + 016    "Convert this in-memory `stl` structure, as read by `decode-binary-stl`, into +
+ + 017    an in-memory hiccup representation of SVG structure, and return it." +
+ + 018    [stl] +
+ + 019    (let [minx (reduce +
+ + 020                 min +
+ + 021                 (map +
+ + 022                   #(reduce min (map :x (:vertices %))) +
+ + 023                   (:facets stl))) +
+ + 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 +
+ + 040       {:xmlns "http://www.w3.org/2000/svg" +
+ + 041        :version "1.2" +
+ + 042        :width (- maxx minx) +
+ + 043        :height (- maxy miny) +
+ + 044        :viewBox (s/join " " (map str [minx miny maxx maxy]))} +
+ + 045       (vec +
+ + 046         (cons +
+ + 047           :g +
+ + 048           (map +
+ + 049             facet->svg-poly +
+ + 050             (:facets stl))))])) +
+ + diff --git a/docs/cloverage/walkmap/vertex.clj.html b/docs/cloverage/walkmap/vertex.clj.html new file mode 100644 index 0000000..382bc40 --- /dev/null +++ b/docs/cloverage/walkmap/vertex.clj.html @@ -0,0 +1,137 @@ + + + + walkmap/vertex.clj + + + + 001  (ns walkmap.vertex +
+ + 002    "Essentially the specification for things we shall consider to be vertices.") +
+ + 003   +
+ + 004  (defn vertex? +
+ + 005    "True if `o` satisfies the conditions for a vertex. That is, essentially, +
+ + 006    that it must rerpresent a two- or three- dimensional vector. A vertex is +
+ + 007    shall be a map having at least the keys `:x` and `:y`, where the value of +
+ + 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] +
+ + 031         (cond +
+ + 032           (not (vertex? o)) (throw (Exception. "Not a vertex!")) +
+ + 033           (:z o) o +
+ + 034           :else (assoc o :z dflt)))))) +
+ + 035   +
+ + 036  (def ensure2d +
+ + 037    "If `o` is a vertex, set its `:z` value to zero; else throw an exception." +
+ + 038    (memoize +
+ + 039      (fn [o] +
+ + 040        (if +
+ + 041          (vertex? o) +
+ + 042          (assoc o :z 0.0) +
+ + 043          (throw (Exception. "Not a vertex!")))))) +
+ + diff --git a/src/walkmap/edge.clj b/src/walkmap/edge.clj index b77547d..9f50281 100644 --- a/src/walkmap/edge.clj +++ b/src/walkmap/edge.clj @@ -2,9 +2,10 @@ "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." - (:require [walkmap.path :refer [path? polygon->path]] + (:require [clojure.math.numeric-tower :as m] + [walkmap.path :refer [path? polygon->path]] [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :refer [vertex?]])) + [walkmap.vertex :refer [ensure3d vertex?]])) (defn edge? "True if `o` satisfies the conditions for a path. A path shall be a map @@ -35,3 +36,47 @@ (polygon? o) (path->edges (polygon->path o)))) +(defn length + "Return the length of the edge `e`." + [e] + (let [start (ensure3d (:start e)) + end (ensure3d (:end e))] + (m/sqrt + (reduce + + + (map + #(m/expt (- (% end) (% start)) 2) + [:x :y :z]))))) + +(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." + [e] + (let [e' {:start (ensure3d (:start e)) :end (ensure3d (:end e))} + l (length e')] + (reduce + merge + {} + (map + (fn [k] + {k (/ (- (k (:end e')) (k (:start e'))) l)}) + [:x :y :z])))) + +(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)) + (rest uvs)))) + +(defn collinear? + "True if edges `e1` and `e2` are collinear with one another." + [e1 e2] + (parallel? + e1 + e2 + {:start (:start e1) :end (:start e2)})) + diff --git a/src/walkmap/geometry.clj b/src/walkmap/geometry.clj index 61baf32..f152298 100644 --- a/src/walkmap/geometry.clj +++ b/src/walkmap/geometry.clj @@ -1,52 +1,19 @@ (ns walkmap.geometry (:require [clojure.math.combinatorics :as combo] [clojure.math.numeric-tower :as m] - [walkmap.edge :as edge] + [walkmap.edge :as e] [walkmap.path :refer [path? polygon->path]] [walkmap.polygon :refer [polygon?]] - [walkmap.vertex :as vertex])) - -(defn collinear? - "True if these vertices `v1`, `v2`, `v3` are colinear; false otherwise." - ;; This is failing... - ;; see http://www.ambrsoft.com/TrigoCalc/Line3D/LineColinear.htm - [v1 v2 v3] - (let [a (m/sqrt (+ (- (:x v2) (:x v1)) (- (:y v2) (:y v1)) (- (:z v2) (:z v1)))) - b (m/sqrt (+ (- (:x v3) (:x v1)) (- (:y v3) (:y v1)) (- (:z v3) (:z v1)))) - c (m/sqrt (+ (- (:x v3) (:x v2)) (- (:y v3) (:y v2)) (- (:z v3) (:z v2))))] - (not - (and - (> (+ a b) c) - (> (+ a c) b) - (> (+ b c) a))))) - -;; (collinear? {:x 0 :y 0 :z 0} {:x 1 :y 1 :z 1} {:x 7 :y 7 :z 7}) -;; (collinear? {:x 0 :y 0 :z 0} {:x 1 :y 2 :z 1} {:x 7 :y 7 :z 7}) -;; (collinear? {:x 0 :y 0 :z 0} {:x 0 :y 2 :z 0} {:x 0 :y 3 :z 0}) - -;; (def v1 {:x 0 :y 0 :z 0}) -;; (def v2 {:x 0 :y 2 :z 0}) -;; (def v3 {:x 0 :y 7 :z 0}) - -;; (def a (m/sqrt (+ (- (:x v2) (:x v1)) (- (:y v2) (:y v1)) (- (:z v2) (:z v1))))) -;; a -;; (def b (m/sqrt (+ (- (:x v3) (:x v1)) (- (:y v3) (:y v1)) (- (:z v3) (:z v1))))) -;; b -;; (def c (m/sqrt (+ (- (:x v3) (:x v2)) (- (:y v3) (:y v2)) (- (:z v3) (:z v2))))) -;; c - -;; (> (+ b c) a) -;; (> (+ a c) b) -;; (> (+ a c) c) + [walkmap.vertex :as v])) (defn on? "True if the vertex `v` is on the edge `e`." [e v] - (let [p (vertex/ensure3d (:start e)) - q (vertex/ensure3d v) - r (vertex/ensure3d (:end e))] + (let [p (v/ensure3d (:start e)) + q (v/ensure3d v) + r (v/ensure3d (:end e))] (and - (collinear? p q r) + (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))) diff --git a/src/walkmap/vertex.clj b/src/walkmap/vertex.clj index 78064ee..c62d1ee 100644 --- a/src/walkmap/vertex.clj +++ b/src/walkmap/vertex.clj @@ -17,24 +17,27 @@ (number? (:y o)) (or (nil? (:z o)) (number? (:z o))))) -(defn ensure3d +(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 `: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." - ([o] - (ensure3d o 0.0)) - ([o dflt] - (cond - (not (vertex? o)) (throw (Exception. "Not a vertex!")) - (:z o) o - :else (assoc o :z dflt)))) + (memoize + (fn + ([o] + (ensure3d o 0.0)) + ([o dflt] + (cond + (not (vertex? o)) (throw (Exception. "Not a vertex!")) + (:z o) o + :else (assoc o :z dflt)))))) -(defn ensure2d +(def ensure2d "If `o` is a vertex, set its `:z` value to zero; else throw an exception." - [o] - (if - (vertex? o) - (assoc o :z 0.0) - (throw (Exception. "Not a vertex!")))) + (memoize + (fn [o] + (if + (vertex? o) + (assoc o :z 0.0) + (throw (Exception. "Not a vertex!")))))) diff --git a/test/walkmap/core_test.clj b/test/walkmap/core_test.clj index 2f97186..b84b538 100644 --- a/test/walkmap/core_test.clj +++ b/test/walkmap/core_test.clj @@ -2,6 +2,6 @@ (:require [clojure.test :refer :all] [walkmap.core :refer :all])) -(deftest a-test - (testing "FIXME, I fail." - (is (= 0 1)))) +;; (deftest a-test +;; (testing "FIXME, I fail." +;; (is (= 0 1)))) diff --git a/test/walkmap/edge_test.clj b/test/walkmap/edge_test.clj new file mode 100644 index 0000000..b919623 --- /dev/null +++ b/test/walkmap/edge_test.clj @@ -0,0 +1,46 @@ +(ns walkmap.edge-test + (:require [clojure.test :refer :all] + [walkmap.edge :refer :all])) + +(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") + )) + +(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)))) + +(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}}) + {: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}}) + {: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}}) + "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}})) + "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}}) + "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}})) + "Should not be!")))