Massive code clean-up, all tests still pass.

...but I'm still struggling to understand how it works and how I use it. Memo to self: document your code better.
This commit is contained in:
Simon Brooke 2024-04-08 22:36:10 +01:00
parent 156775fc79
commit cb5041e684
11 changed files with 235 additions and 250 deletions

View file

@ -3,7 +3,6 @@
An edge is a line segment having just a start and an end, with no intervening An edge is a line segment having just a start and an end, with no intervening
nodes." nodes."
(:require [clojure.math.numeric-tower :as m] (:require [clojure.math.numeric-tower :as m]
[cc.journeyman.walkmap.utils :as u]
[cc.journeyman.walkmap.vertex :refer [canonicalise check-vertex ensure2d ensure3d vertex vertex= vertex?]])) [cc.journeyman.walkmap.vertex :refer [canonicalise check-vertex ensure2d ensure3d vertex vertex= vertex?]]))
(defn edge (defn edge
@ -149,7 +148,7 @@
not an edge." not an edge."
([e1 e2] ([e1 e2]
(intersection2d e1 e2 :x :y :z)) (intersection2d e1 e2 :x :y :z))
([e1 e2 c1 c2 c3] ([e1 e2 c1 c2 _c3]
(if (and (edge? e1) (edge? e2)) (if (and (edge? e1) (edge? e2))
(when (when
(overlaps2d? e1 e2) ;; relatively cheap check (overlaps2d? e1 e2) ;; relatively cheap check

View file

@ -2,36 +2,36 @@
"An interface between walkmap and microworld, to allow use of microworld "An interface between walkmap and microworld, to allow use of microworld
functionality to model things like rainfall, soil fertility, settlement functionality to model things like rainfall, soil fertility, settlement
and so on." and so on."
(:require [clojure.edn :as edn :only [read]] (:require [cc.journeyman.walkmap.polygon :refer [rectangle]]
[clojure.java.io :as io]
[clojure.string :as s]
[taoensso.timbre :as l]
[cc.journeyman.walkmap.edge :as e]
[cc.journeyman.walkmap.polygon :as p :only [rectangle]]
[cc.journeyman.walkmap.superstructure :refer [store]] [cc.journeyman.walkmap.superstructure :refer [store]]
[cc.journeyman.walkmap.tag :as t :only [tag]] [cc.journeyman.walkmap.tag :refer [tag]]
[cc.journeyman.walkmap.vertex :as v :only [check-vertex vertex vertex?]] [cc.journeyman.walkmap.vertex :refer [check-vertex vertex]]
[cc.journeyman.walkmap.utils :as u :only [truncate]])) [cc.journeyman.walkmap.utils :refer [truncate]]
[clojure.edn :as edn :only [read]]
[clojure.java.io :refer [reader]]
[taoensso.timbre :refer [error]])
(:import [clojure.lang Keyword Map]
[java.io PushbackReader]))
(defn cell->polygon (defn cell->polygon
"From this MicroWorld `cell`, construct a walkmap polygon (specifically, "From this MicroWorld `cell`, construct a walkmap polygon (specifically,
a rectangle. If `scale-vector` passed and is a vertex, scale all the vertices a rectangle). If `scale-vector` passed and is a vertex, scale all the vertices
in the cell by that vector." in the cell by that vector."
([cell] ([cell]
(cell->polygon cell (v/vertex 1 1 1))) (cell->polygon cell (vertex 1 1 1)))
([cell scale-vector] ([cell scale-vector]
(t/tag (tag
(assoc (assoc
(merge (merge
cell cell
(let [w (* (:x cell) (:x (v/check-vertex scale-vector))) (let [w (* (:x cell) (:x (check-vertex scale-vector)))
s (* (:y cell) (:y scale-vector)) s (* (:y cell) (:y scale-vector))
e (+ w (:x scale-vector)) e (+ w (:x scale-vector))
n (+ s (:y scale-vector)) n (+ s (:y scale-vector))
z (* (:altitude cell) (:z scale-vector))] z (* (:altitude cell) (:z scale-vector))]
(p/rectangle (rectangle
(v/vertex s w z) (vertex s w z)
(v/vertex n e z)))) (vertex n e z))))
:walkmap.id/id :walkmap.id/id
(keyword (gensym "mw-cell"))) (keyword (gensym "mw-cell")))
(:state cell)))) (:state cell))))
@ -42,23 +42,23 @@
compute-heavy that it's much more sensible to do it in batch mode. So the compute-heavy that it's much more sensible to do it in batch mode. So the
better plan is to be able to pull the output from MicroWorld - as an EDN better plan is to be able to pull the output from MicroWorld - as an EDN
structure - into a walkmap superstructure." structure - into a walkmap superstructure."
([filename] ([^String filename]
(load-microworld-edn filename :mw)) (load-microworld-edn filename :mw))
([filename map-kind] ([^String filename ^Keyword map-kind]
(when-not (when-not
(keyword? map-kind) (keyword? map-kind)
(throw (IllegalArgumentException. (throw (IllegalArgumentException.
(u/truncate (truncate
(str "Must be a keyword: " (or map-kind "nil")) 80)))) (format "Must be a keyword: %s." (or map-kind "nil")) 80))))
(load-microworld-edn filename map-kind nil)) (load-microworld-edn filename map-kind nil))
([filename mapkind superstucture] ([^String filename ^Keyword mapkind ^Map superstucture]
(load-microworld-edn filename mapkind superstucture (v/vertex 1 1 1))) (load-microworld-edn filename mapkind superstucture (vertex 1 1 1)))
([filename map-kind superstructure scale-vertex] ([^String filename ^Keyword _map-kind ^Map superstructure ^Map _scale-vertex]
(let [mw (try (let [mw (try
(with-open [r (io/reader filename)] (with-open [r (reader filename)]
(edn/read (java.io.PushbackReader. r))) (edn/read (PushbackReader. r)))
(catch RuntimeException e (catch RuntimeException e
(l/error "Error parsing edn file '%s': %s\n" (error "Error parsing edn file '%s': %s\n"
filename (.getMessage e)))) filename (.getMessage e))))
polys (reduce polys (reduce
concat concat

View file

@ -2,11 +2,9 @@
"Essentially the specification for things we shall consider to be path. "Essentially the specification for things we shall consider to be path.
**Note that** for these purposes `path` means any continuous linear **Note that** for these purposes `path` means any continuous linear
feature, where such features specifically include watercourses." feature, where such features specifically include watercourses."
(:require [clojure.string :as s] (:require [cc.journeyman.walkmap.edge :as e]
[cc.journeyman.walkmap.edge :as e] [cc.journeyman.walkmap.polygon :refer [polygon?]]
[cc.journeyman.walkmap.polygon :refer [check-polygon polygon?]] [cc.journeyman.walkmap.utils :refer [check-kind-type check-kind-type-seq]]
[cc.journeyman.walkmap.tag :refer [tag tags]]
[cc.journeyman.walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]]
[cc.journeyman.walkmap.vertex :refer [check-vertices vertex?]])) [cc.journeyman.walkmap.vertex :refer [check-vertices vertex?]]))
(defn path? (defn path?

View file

@ -139,7 +139,7 @@
projection of this rectangle on that plane?" projection of this rectangle on that plane?"
[vertex rectangle] [vertex rectangle]
(let [xo (sort-by :x (:vertices rectangle)) (let [xo (sort-by :x (:vertices rectangle))
yo (sort-by :x (:vertices rectangle))] yo (sort-by :y (:vertices rectangle))]
(and (and
(< (:x (first xo)) (:x vertex) (:x (last xo))) (< (:x (first xo)) (:x vertex) (:x (last xo)))
(< (:y (first yo)) (:y vertex) (:y (last yo)))))) (< (:y (first yo)) (:y vertex) (:y (last yo))))))

View file

@ -1,16 +1,11 @@
(ns cc.journeyman.walkmap.read-svg (ns cc.journeyman.walkmap.read-svg
"Utility functions for scalable vector graphics (SVG) into walkmap "Utility functions for scalable vector graphics (SVG) into walkmap
structures." structures."
(:require [clojure.data.zip :as dz] (:require [clojure.java.io :as io]
[clojure.data.zip.xml :as zx]
[clojure.java.io :as io]
[clojure.string :as s] [clojure.string :as s]
[clojure.xml :as x] [clojure.xml :as x]
[clojure.zip :as z]
[taoensso.timbre :as l]
[cc.journeyman.walkmap.path :refer [path]] [cc.journeyman.walkmap.path :refer [path]]
[cc.journeyman.walkmap.tag :refer [tag]] [cc.journeyman.walkmap.tag :refer [tag]]
[cc.journeyman.walkmap.utils :refer [kind-type truncate]]
[cc.journeyman.walkmap.vertex :refer [vertex vertex?]])) [cc.journeyman.walkmap.vertex :refer [vertex vertex?]]))
(defn upper-case? (defn upper-case?
@ -91,7 +86,7 @@
;; quite small XML files. So I've implemented my own solution. ;; quite small XML files. So I've implemented my own solution.
([file-name] ([file-name]
(read-svg file-name nil)) (read-svg file-name nil))
([file-name map-kind] ([file-name _map-kind]
(let [xml (x/parse (io/file file-name)) (let [xml (x/parse (io/file file-name))
paths (progeny xml #(= (:tag %) :path))] paths (progeny xml #(= (:tag %) :path))]
(remove nil? (map path-elt->path paths))))) (remove nil? (map path-elt->path paths)))))

View file

@ -1,16 +1,15 @@
(ns cc.journeyman.walkmap.routing (ns cc.journeyman.walkmap.routing
"Finding optimal routes to traverse a map." "Finding optimal routes to traverse a map."
(:require [clojure.math.numeric-tower :as m :only [expt]] (:require [clojure.math.numeric-tower :refer [expt]]
[clojure.set :refer [intersection]] [clojure.set :refer [intersection]]
[clojure.string :as cs :only [join]]
[search.core :refer [a*]]
[cc.journeyman.walkmap.edge :as e] [cc.journeyman.walkmap.edge :as e]
[cc.journeyman.walkmap.path :as p] [cc.journeyman.walkmap.path :as p]
[cc.journeyman.walkmap.polygon :as q] [cc.journeyman.walkmap.polygon :as q]
[cc.journeyman.walkmap.superstructure :as s] [cc.journeyman.walkmap.superstructure :as s]
[cc.journeyman.walkmap.tag :as t] [cc.journeyman.walkmap.tag :refer [tags]]
[cc.journeyman.walkmap.utils :as u] [cc.journeyman.walkmap.vertex :as v]
[cc.journeyman.walkmap.vertex :as v])) [search.core :refer [a*]]
[taoensso.timbre :refer [debug]]))
;; Breadth first search is a good algorithm for terrain in which all steps have ;; Breadth first search is a good algorithm for terrain in which all steps have
;; equal, but in our world (like the real world), they don't. ;; equal, but in our world (like the real world), they don't.
@ -37,6 +36,11 @@
of traversals. Dynamic, because we will want to tune this." of traversals. Dynamic, because we will want to tune this."
-2) -2)
(def ^:dynamic *untraversable*
"The set of all tags which may indicate that a polygon should not be
traversed."
#{:no-traversal})
(defn traversable? (defn traversable?
"True if this `object` is a polygon which can be considered as part of "True if this `object` is a polygon which can be considered as part of
the walkmap." the walkmap."
@ -44,7 +48,7 @@
(and (and
(q/polygon? object) (q/polygon? object)
(:centre object) (:centre object)
(not (t/tagged? object :no-traversal)))) (empty? (intersection (tags object) *untraversable*))))
(declare traversal-cost) (declare traversal-cost)
@ -64,7 +68,8 @@
(defn barriers-crossed (defn barriers-crossed
"Search superstructure `s` and return a sequence of barriers, if any, which "Search superstructure `s` and return a sequence of barriers, if any, which
obstruct traversal from vertex `from` to vertex `to`." obstruct traversal from vertex `from` to vertex `to`."
[from to s] [from to _s]
(debug (format "barriers-crossed called with vertices\n\tfrom: %s\n\tto: %s" from to))
;; TODO: implement ;; TODO: implement
'()) '())
@ -74,7 +79,8 @@
of superstructure `s`. If there's a bridge, ferry or other crossing mechanism of superstructure `s`. If there's a bridge, ferry or other crossing mechanism
in `s` at the intersection of the vertex and the barrier, then the penalty in `s` at the intersection of the vertex and the barrier, then the penalty
should be substantially less than it would otherwise be." should be substantially less than it would otherwise be."
[barrier from to s] [barrier from to _s]
(debug (format "crossing-penalty called with arguments\n\tbarrier: %s\n\tfrom: %s\n\tto: %s" barrier from to))
;; TODO: implement ;; TODO: implement
0) 0)
@ -83,7 +89,7 @@
[edge] [edge]
(let [g (:z (e/unit-vector edge))] (let [g (:z (e/unit-vector edge))]
(if (pos? g) (if (pos? g)
(m/expt (inc g) *gradient-exponent*) (expt (inc g) *gradient-exponent*)
1))) 1)))
;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 0))) ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 0)))
@ -112,7 +118,7 @@
[from to s] [from to s]
(let [best (best-road from to s)] (let [best (best-road from to s)]
(when (:traversals best) (when (:traversals best)
(m/expt (:traversals best) *traversals-exponent*)))) (expt (:traversals best) *traversals-exponent*))))
(defn traversal-cost (defn traversal-cost
"Return the traversal cost of the edge represented by the vertices `from`, "Return the traversal cost of the edge represented by the vertices `from`,
@ -130,7 +136,7 @@
(gradient-cost edge)) (gradient-cost edge))
(reduce + (reduce +
(map (map
#(crossing-penalty [% from to s]) #(crossing-penalty % from to s)
(barriers-crossed from to s)))) (barriers-crossed from to s))))
(or (road-bonus from to s) 1))))) (or (road-bonus from to s) 1)))))
@ -177,10 +183,9 @@
(let [r (s/nearest s target traversable? search-radius)] (let [r (s/nearest s target traversable? search-radius)]
(when-not r (throw (when-not r (throw
(Exception. (Exception.
(cs/join " " ["Unable to find walkable facet within" (format "Unable to find walkable facet within %s of %s"
search-radius search-radius
"of" target))))
target]))))
r)) r))
(defn route (defn route

View file

@ -1,19 +1,18 @@
(ns cc.journeyman.walkmap.stl (ns cc.journeyman.walkmap.stl
"Utility functions dealing with stereolithography (STL) files. Not a stable API yet!" "Utility functions dealing with stereolithography (STL) files. Not a stable API yet!"
(:require [clojure.java.io :as io :refer [file output-stream input-stream]] (:require
[clojure.string :as s] [cc.journeyman.walkmap.ocean :refer [ocean?]]
[me.raynes.fs :as fs]
[org.clojars.smee.binary.core :as b]
[taoensso.timbre :as l]
[cc.journeyman.walkmap.edge :as e]
[cc.journeyman.walkmap.ocean :as o]
[cc.journeyman.walkmap.polygon :refer [centre gradient polygon?]] [cc.journeyman.walkmap.polygon :refer [centre gradient polygon?]]
[cc.journeyman.walkmap.superstructure :refer [store]] [cc.journeyman.walkmap.superstructure :refer [store]]
[cc.journeyman.walkmap.tag :refer [tag]] [cc.journeyman.walkmap.tag :refer [tag]]
[cc.journeyman.walkmap.utils :as u] [cc.journeyman.walkmap.utils :refer [truncate]]
[cc.journeyman.walkmap.vertex :as v]) [cc.journeyman.walkmap.vertex :as v]
(:import org.clojars.smee.binary.core.BinaryIO [clojure.lang.io :refer [input-stream]]
java.io.DataInput)) [clojure.string :as s]
[me.raynes.fs :refer [base-name split-ext]]
[org.clojars.smee.binary.core :as b]
[taoensso.timbre :refer [debug]])
(:import [clojure.lang Keyword]))
(defn stl? (defn stl?
"True if `o` is recogniseable as an STL structure. An STL structure must "True if `o` is recogniseable as an STL structure. An STL structure must
@ -64,13 +63,13 @@
(defaults to `:height`). It is an error, and an exception will be thrown, if (defaults to `:height`). It is an error, and an exception will be thrown, if
`map-kind` is not a keyword." `map-kind` is not a keyword."
([o] (canonicalise o :height)) ([o] (canonicalise o :height))
([o map-kind] ([o ^Keyword map-kind]
(canonicalise o map-kind (v/vertex 1 1 1))) (canonicalise o map-kind (v/vertex 1 1 1)))
([o map-kind scale-vertex] ([o ^Keyword map-kind scale-vertex]
(when-not (when-not
(keyword? map-kind) (keyword? map-kind)
(throw (IllegalArgumentException. (throw (IllegalArgumentException.
(u/truncate (str "Must be a keyword: " (or map-kind "nil")) 80)))) (truncate (str "Must be a keyword: " (or map-kind "nil")) 80))))
(cond (cond
(and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o) (and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o)
;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?` ;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?`
@ -92,13 +91,13 @@
(:vertices o) (:vertices o)
map-kind)) map-kind))
:facet map-kind)))] :facet map-kind)))]
(if (o/ocean? f) (if (ocean? f)
(tag f :ocean :no-traversal) (tag f :ocean :no-traversal)
f)) f))
;; if it has a value for :x it's a vertex, but it may not yet conform ;; if it has a value for :x it's a vertex, but it may not yet conform
;; to `vertex?`; it should also be scaled using the scale-vertex, if any. ;; to `vertex?`; it should also be scaled using the scale-vertex, if any.
(:x o) (let [c (v/canonicalise o)] (:x o) (let [c (v/canonicalise o)]
(if scale-vertex (if (v/vertex? scale-vertex)
(v/vertex* c scale-vertex) (v/vertex* c scale-vertex)
c)) c))
;; shouldn't happen ;; shouldn't happen
@ -122,18 +121,14 @@
**NOTE** that we've no way of verifying that the input file is binary STL **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." data, if it is not this will run but will return garbage."
([filename] ([^String filename]
(decode-binary-stl filename :height)) (decode-binary-stl filename :height))
([filename map-kind] ([^String filename ^Keyword map-kind]
(when-not
(keyword? map-kind)
(throw (IllegalArgumentException.
(u/truncate (str "Must be a keyword: " (or map-kind "nil")) 80))))
(decode-binary-stl filename map-kind nil)) (decode-binary-stl filename map-kind nil))
([filename mapkind superstucture] ([^String filename ^Keyword mapkind superstucture]
(decode-binary-stl filename mapkind superstucture (v/vertex 1 1 1))) (decode-binary-stl filename mapkind superstucture (v/vertex 1 1 1)))
([filename map-kind superstructure scale-vertex] ([^String filename ^Keyword map-kind superstructure scale-vertex]
(let [in (io/input-stream filename) (let [in (input-stream filename)
stl (canonicalise (b/decode binary-stl in) map-kind scale-vertex)] stl (canonicalise (b/decode binary-stl in) map-kind scale-vertex)]
(if (if
(map? superstructure) (map? superstructure)
@ -175,12 +170,12 @@
"Write an `stl` structure as read by `decode-binary-stl` to this "Write an `stl` structure as read by `decode-binary-stl` to this
`filename` as ASCII encoded STL." `filename` as ASCII encoded STL."
([filename stl] ([filename stl]
(let [b (fs/base-name filename true)] (let [b (base-name filename true)]
(write-ascii-stl (write-ascii-stl
filename stl filename stl
(subs b 0 (or (s/index-of b ".") (count b)))))) (subs b 0 (or (s/index-of b ".") (count b))))))
([filename stl solidname] ([filename stl solidname]
(l/debug "Solid name is " solidname) (debug "Solid name is " solidname)
(spit (spit
filename filename
(stl->ascii stl solidname)))) (stl->ascii stl solidname))))
@ -190,7 +185,7 @@
`out-filename`, if specified; otherwise, to a file with the same basename `out-filename`, if specified; otherwise, to a file with the same basename
as `in-filename` but the extension `.ascii.stl`." as `in-filename` but the extension `.ascii.stl`."
([in-filename] ([in-filename]
(let [[_ ext] (fs/split-ext in-filename)] (let [[_ ext] (split-ext in-filename)]
(binary-stl-to-ascii (binary-stl-to-ascii
in-filename in-filename
(str (str

View file

@ -3,15 +3,16 @@
(:require [clojure.walk :refer [postwalk]] (:require [clojure.walk :refer [postwalk]]
[taoensso.timbre :as l] [taoensso.timbre :as l]
[cc.journeyman.walkmap.edge :refer [edge length]] [cc.journeyman.walkmap.edge :refer [edge length]]
[cc.journeyman.walkmap.path :as p] ;; [cc.journeyman.walkmap.path :as p]
[cc.journeyman.walkmap.polygon :as q] ;; [cc.journeyman.walkmap.polygon :as q]
[cc.journeyman.walkmap.utils :as u] [cc.journeyman.walkmap.utils :as u]
[cc.journeyman.walkmap.vertex :as v])) [cc.journeyman.walkmap.vertex :as v])
(:import [clojure.lang IFn Map]))
;; TODO: Think about reification/dereification. How can we cull a polygon, if ;; TODO: Think about reification/dereification. How can we cull a polygon, if
;; some vertices still index it? I *think* that what's needed is that when ;; some vertices still index it? I *think* that what's needed is that when
;; we store something in the superstructure, we replace all its vertices (and ;; we store something in the superstructure, we replace all its vertices (and
;; other dependent structures, if any with their ids - as well as, obviously, ;; other dependent structures, if any) with their ids - as well as, obviously,
;; adding/merging those vertices/dependent structures into the superstructure ;; adding/merging those vertices/dependent structures into the superstructure
;; as first class objects in themselves. That means, for each identified thing, ;; as first class objects in themselves. That means, for each identified thing,
;; the superstructure only contains one copy of it. ;; the superstructure only contains one copy of it.
@ -53,7 +54,7 @@
2. `o` is not a map; 2. `o` is not a map;
3. `o` does not have a value for the key `:walkmap.id/id`; 3. `o` does not have a value for the key `:walkmap.id/id`;
4. `v` is not a vertex." 4. `v` is not a vertex."
[s o v] [^Map s ^Map o ^Map v]
(if-not (v/vertex? o) (if-not (v/vertex? o)
(if (:walkmap.id/id o) (if (:walkmap.id/id o)
(if (v/vertex? v) (if (v/vertex? v)
@ -161,9 +162,9 @@
1. `s` is not a map; 1. `s` is not a map;
2. `o` is not a recognisable walkmap object" 2. `o` is not a recognisable walkmap object"
([o] ([^Map o]
(store o {})) (store o {}))
([o s] ([^Map o ^Map s]
(when-not (:walkmap.id/id o) (when-not (:walkmap.id/id o)
(throw (throw
(IllegalArgumentException. (IllegalArgumentException.
@ -188,13 +189,13 @@
have properties which will be denormalised by `store`, and therefore do not have properties which will be denormalised by `store`, and therefore do not
have to restored with `retrieve`. If properties are added to vertices have to restored with `retrieve`. If properties are added to vertices
whose values are objects, then this will have to be rewritten." whose values are objects, then this will have to be rewritten."
([s minv maxv] ([^Map s ^Map minv ^Map maxv]
(search-vertices s minv maxv false)) (search-vertices s minv maxv false))
([s minv maxv d2?] ([s minv maxv d2?]
(let [minv' (if d2? (assoc minv :z Double/NEGATIVE_INFINITY) minv) (let [minv' (if d2? (assoc minv :z Double/NEGATIVE_INFINITY) minv)
maxv' (if d2? (assoc maxv :z Double/POSITIVE_INFINITY) maxv)] maxv' (if d2? (assoc maxv :z Double/POSITIVE_INFINITY) maxv)]
(filter (filter
#(v/within-box? % minv maxv) #(v/within-box? % minv' maxv')
(filter #(= (:kind %) :vertex) (vals s)))))) (filter #(= (:kind %) :vertex) (vals s))))))
(defn nearest (defn nearest
@ -205,7 +206,9 @@
WARNING: currently only returns objects which have a defined `:centre` WARNING: currently only returns objects which have a defined `:centre`
(but most of the significant objects we have do)." (but most of the significant objects we have do)."
[s target filter-fn radius] ([^Map s ^Map target ^Number radius]
(nearest s target :centre radius))
([^Map s ^Map target ^IFn filter-fn ^Number radius]
(let [minv (v/vertex (let [minv (v/vertex
(- (:x (v/check-vertex target)) radius) (- (:x (v/check-vertex target)) radius)
(- (:y target) radius) (- (or (:z target) 0) radius)) (- (:y target) radius) (- (or (:z target) 0) radius))
@ -218,6 +221,8 @@
(first (first
(sort-by (sort-by
#(length (edge target (:centre %))) #(length (edge target (:centre %)))
(filter
filter-fn
(filter (filter
:centre :centre
(map #(retrieve % s) (map #(retrieve % s)
@ -233,16 +238,16 @@
(set (set
(map (map
:walkmap.id/id :walkmap.id/id
(search-vertices s minv maxv)))))))))))) (search-vertices s minv maxv))))))))))))))
(defn touching (defn touching
"Return a sequence of all objects in superstructure `s` which are "Return a sequence of all objects in superstructure `s` which are
indexed as touching the vertex `v`." indexed as touching the vertex `v`."
([vertex s] ([^Map vertex ^Map s]
(map (map
#(retrieve % s) #(retrieve % s)
(set (-> s :vertex-index (:walkmap.id/id (v/check-vertex vertex)) keys)))) (set (-> s :vertex-index (:walkmap.id/id (v/check-vertex vertex)) keys))))
([vertex filter-fn s] ([^Map vertex ^IFn filter-fn ^Map s]
(filter (filter
filter-fn filter-fn
(touching vertex s)))) (touching vertex s))))
@ -251,9 +256,9 @@
"Return a sequence of all those objects in superstructure `s` which share "Return a sequence of all those objects in superstructure `s` which share
at least one vertex with `target`, and which are matched by `filter-fn` at least one vertex with `target`, and which are matched by `filter-fn`
if supplied." if supplied."
([target s] ([^Map target ^Map s]
(neighbours identity s)) (neighbours target identity s))
([target filter-fn s] ([^Map target ^IFn filter-fn ^Map s]
(remove (remove
#(= target %) #(= target %)
(reduce (reduce
@ -266,7 +271,7 @@
"Return a sequence of the ids all those objects in superstructure `s` which "Return a sequence of the ids all those objects in superstructure `s` which
share at least one vertex with `target`, and which are matched by share at least one vertex with `target`, and which are matched by
`filter-fn` if supplied." `filter-fn` if supplied."
([target s] ([^Map target ^Map s]
(neighbour-ids target identity s)) (neighbour-ids target identity s))
([target filter-fn s] ([^Map target ^IFn filter-fn ^Map s]
(map :walkmap.id/id (neighbours target filter-fn s)))) (map :walkmap.id/id (neighbours target filter-fn s))))

View file

@ -2,16 +2,12 @@
"Utility functions for writing stereolithography (STL) files (and possibly, "Utility functions for writing stereolithography (STL) files (and possibly,
later, other geometry files of interest to us) as scalable vector graphics later, other geometry files of interest to us) as scalable vector graphics
(SVG)." (SVG)."
(:require [clojure.java.io :as io] (:require [clojure.string :as s]
[clojure.string :as s]
[clojure.xml :as x]
[dali.io :as neatly-folded-clock] [dali.io :as neatly-folded-clock]
[hiccup.core :refer [html]] [hiccup.core :refer [html]]
[taoensso.timbre :as l :refer [info error spy]] [taoensso.timbre :refer [info]]
[cc.journeyman.walkmap.ocean :refer [cull-ocean-facets]] [cc.journeyman.walkmap.ocean :refer [cull-ocean-facets]]
[cc.journeyman.walkmap.polygon :refer [polygon?]] [cc.journeyman.walkmap.stl :refer [decode-binary-stl]]))
[cc.journeyman.walkmap.stl :refer [decode-binary-stl]]
[cc.journeyman.walkmap.vertex :refer [vertex?]]))
(def ^:dynamic *preferred-svg-render* (def ^:dynamic *preferred-svg-render*
"Mainly for debugging dali; switch SVG renderer to use. Expected values: "Mainly for debugging dali; switch SVG renderer to use. Expected values:
@ -88,7 +84,7 @@
(map (map
#(reduce max (map :y (:vertices %))) #(reduce max (map :y (:vertices %)))
(:facets stl)))] (:facets stl)))]
(l/info "Generating SVG for " *preferred-svg-render* " renderer") (info "Generating SVG for " *preferred-svg-render* " renderer")
(case *preferred-svg-render* (case *preferred-svg-render*
:hiccup (hiccup-stl->svg stl minx maxx miny maxy) :hiccup (hiccup-stl->svg stl minx maxx miny maxy)
:dali (dali-stl->svg stl minx maxx miny maxy) :dali (dali-stl->svg stl minx maxx miny maxy)
@ -102,7 +98,7 @@
(stl->svg (cull-ocean-facets (decode-binary-stl in-filename)))) (stl->svg (cull-ocean-facets (decode-binary-stl in-filename))))
([in-filename out-filename] ([in-filename out-filename]
(let [s (binary-stl-file->svg in-filename)] (let [s (binary-stl-file->svg in-filename)]
(l/info "Emitting SVG with " *preferred-svg-render* " renderer") (info "Emitting SVG with " *preferred-svg-render* " renderer")
(case *preferred-svg-render* (case *preferred-svg-render*
:dali (neatly-folded-clock/render-svg s out-filename) :dali (neatly-folded-clock/render-svg s out-filename)
:hiccup (spit out-filename (html s)) :hiccup (spit out-filename (html s))

View file

@ -4,7 +4,8 @@
This is in an attempt to avoid name clashes with other uses of this key." This is in an attempt to avoid name clashes with other uses of this key."
(:require [clojure.set :refer [difference union]] (:require [clojure.set :refer [difference union]]
[taoensso.timbre :as l] [taoensso.timbre :as l]
[cc.journeyman.walkmap.utils :refer [kind-type]])) [cc.journeyman.walkmap.utils :refer [kind-type]])
(:import [clojure.lang Map]))
(defn tagged? (defn tagged?
"True if this `object` is tagged with each of these `tags`. It is an error "True if this `object` is tagged with each of these `tags`. It is an error
@ -12,10 +13,7 @@
1. `object` is not a map; 1. `object` is not a map;
2. any of `tags` is not a keyword." 2. any of `tags` is not a keyword."
[object & tags] [^Map object & tags]
(when-not (map? object)
(throw (IllegalArgumentException.
(str "Must be a map: " (kind-type object)))))
(let [tags' (flatten tags)] (let [tags' (flatten tags)]
(when-not (every? keyword? tags') (when-not (every? keyword? tags')
(throw (IllegalArgumentException. (throw (IllegalArgumentException.
@ -35,11 +33,8 @@
It's legal to include sequences of keywords in `tags`, so that users can do It's legal to include sequences of keywords in `tags`, so that users can do
useful things like `(tag obj (map keyword some-strings))`." useful things like `(tag obj (map keyword some-strings))`."
[object & tags] [^Map object & tags]
(l/debug "Tagging" (kind-type object) "with" tags) (l/debug "Tagging" (kind-type object) "with" tags)
(when-not (map? object)
(throw (IllegalArgumentException.
(str "Must be a map: " (kind-type object)))))
(let [tags' (flatten tags)] (let [tags' (flatten tags)]
(when-not (every? keyword? tags') (when-not (every? keyword? tags')
(throw (IllegalArgumentException. (throw (IllegalArgumentException.
@ -57,10 +52,7 @@
1. `object` is not a map; 1. `object` is not a map;
2. any of `tags` is not a keyword or sequence of keywords." 2. any of `tags` is not a keyword or sequence of keywords."
[object & tags] [^Map object & tags]
(when-not (map? object)
(throw (IllegalArgumentException.
(str "Must be a map: " (kind-type object)))))
(let [tags' (flatten tags)] (let [tags' (flatten tags)]
(when-not (every? keyword? tags') (when-not (every? keyword? tags')
(throw (IllegalArgumentException. (throw (IllegalArgumentException.

View file

@ -3,16 +3,16 @@
(:require [clojure.edn :as edn :only [read]] (:require [clojure.edn :as edn :only [read]]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.math.numeric-tower :as m] [clojure.math.numeric-tower :as m]
[clojure.set :refer [union]]
[clojure.string :as s])) [clojure.string :as s]))
(defn deep-merge (defn deep-merge
"Recursively merges maps. If vals are not maps, the last value wins." "Recursively merges maps. If vals are not maps, the last value wins."
;; TODO: not my implementation, not sure I entirely trust it. ;; TODO: not my implementation, not sure I entirely trust it.
;; TODO TODO: if we are to successfully merge walkmap objects, we must
;; return, on each object, the union of its tags if any.
[& vals] [& vals]
(if (every? map? vals) (if (every? map? vals)
(apply merge-with deep-merge vals) (let [tags (apply union (map :cc.journeyman.walkmap.tag/tags vals))]
(assoc (apply merge-with deep-merge vals) :cc.journeyman.walkmap.tag/tags tags))
(last vals))) (last vals)))
(defn truncate (defn truncate
@ -27,20 +27,20 @@
"Identify the type of an `object`, e.g. for logging. If it has a `:kind` key, "Identify the type of an `object`, e.g. for logging. If it has a `:kind` key,
it's one of ours, and that's what we want. Otherwise, we want its type; but it's one of ours, and that's what we want. Otherwise, we want its type; but
the type of `nil` is `nil`, which doesn't get printed when assembling error the type of `nil` is `nil`, which doesn't get printed when assembling error
,essages, so return \"nil\"." messages, so return \"nil\"."
[object] [object]
(or (:kind object) (type object) "nil")) (or (:kind object) (type object) "nil"))
(defn =ish (defn =ish
"True if numbers `n1`, `n2` are roughly equal; that is to say, equal to "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to
within `tolerance` (defaults to one part in one hundred thousand)." within `tolerance` (defaults to one part in one hundred thousand)."
([n1 n2] ([^Number n1 ^Number n2]
(if (and (number? n1) (number? n2)) (if (and (number? n1) (number? n2))
(let [m (m/abs (min n1 n2)) (let [m (m/abs (min n1 n2))
t (if (zero? m) 0.00001 (* 0.00001 m))] t (if (zero? m) 0.00001 (* 0.00001 m))]
(=ish n1 n2 t)) (=ish n1 n2 t))
(= n1 n2))) (= n1 n2)))
([n1 n2 tolerance] ([^Number n1 ^Number n2 ^Number tolerance]
(if (and (number? n1) (number? n2)) (if (and (number? n1) (number? n2))
(< (m/abs (- n1 n2)) tolerance) (< (m/abs (- n1 n2)) tolerance)
(= n1 n2)))) (= n1 n2))))