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:
parent
156775fc79
commit
cb5041e684
|
@ -3,7 +3,6 @@
|
|||
An edge is a line segment having just a start and an end, with no intervening
|
||||
nodes."
|
||||
(: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?]]))
|
||||
|
||||
(defn edge
|
||||
|
@ -149,7 +148,7 @@
|
|||
not an edge."
|
||||
([e1 e2]
|
||||
(intersection2d e1 e2 :x :y :z))
|
||||
([e1 e2 c1 c2 c3]
|
||||
([e1 e2 c1 c2 _c3]
|
||||
(if (and (edge? e1) (edge? e2))
|
||||
(when
|
||||
(overlaps2d? e1 e2) ;; relatively cheap check
|
||||
|
|
|
@ -2,36 +2,36 @@
|
|||
"An interface between walkmap and microworld, to allow use of microworld
|
||||
functionality to model things like rainfall, soil fertility, settlement
|
||||
and so on."
|
||||
(:require [clojure.edn :as edn :only [read]]
|
||||
[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]]
|
||||
(:require [cc.journeyman.walkmap.polygon :refer [rectangle]]
|
||||
[cc.journeyman.walkmap.superstructure :refer [store]]
|
||||
[cc.journeyman.walkmap.tag :as t :only [tag]]
|
||||
[cc.journeyman.walkmap.vertex :as v :only [check-vertex vertex vertex?]]
|
||||
[cc.journeyman.walkmap.utils :as u :only [truncate]]))
|
||||
[cc.journeyman.walkmap.tag :refer [tag]]
|
||||
[cc.journeyman.walkmap.vertex :refer [check-vertex vertex]]
|
||||
[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
|
||||
"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."
|
||||
([cell]
|
||||
(cell->polygon cell (v/vertex 1 1 1)))
|
||||
(cell->polygon cell (vertex 1 1 1)))
|
||||
([cell scale-vector]
|
||||
(t/tag
|
||||
(tag
|
||||
(assoc
|
||||
(merge
|
||||
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))
|
||||
e (+ w (:x scale-vector))
|
||||
n (+ s (:y scale-vector))
|
||||
z (* (:altitude cell) (:z scale-vector))]
|
||||
(p/rectangle
|
||||
(v/vertex s w z)
|
||||
(v/vertex n e z))))
|
||||
(rectangle
|
||||
(vertex s w z)
|
||||
(vertex n e z))))
|
||||
:walkmap.id/id
|
||||
(keyword (gensym "mw-cell")))
|
||||
(:state cell))))
|
||||
|
@ -42,23 +42,23 @@
|
|||
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
|
||||
structure - into a walkmap superstructure."
|
||||
([filename]
|
||||
([^String filename]
|
||||
(load-microworld-edn filename :mw))
|
||||
([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))))
|
||||
(truncate
|
||||
(format "Must be a keyword: %s." (or map-kind "nil")) 80))))
|
||||
(load-microworld-edn filename map-kind nil))
|
||||
([filename mapkind superstucture]
|
||||
(load-microworld-edn filename mapkind superstucture (v/vertex 1 1 1)))
|
||||
([filename map-kind superstructure scale-vertex]
|
||||
([^String filename ^Keyword mapkind ^Map superstucture]
|
||||
(load-microworld-edn filename mapkind superstucture (vertex 1 1 1)))
|
||||
([^String filename ^Keyword _map-kind ^Map superstructure ^Map _scale-vertex]
|
||||
(let [mw (try
|
||||
(with-open [r (io/reader filename)]
|
||||
(edn/read (java.io.PushbackReader. r)))
|
||||
(with-open [r (reader filename)]
|
||||
(edn/read (PushbackReader. r)))
|
||||
(catch RuntimeException e
|
||||
(l/error "Error parsing edn file '%s': %s\n"
|
||||
(error "Error parsing edn file '%s': %s\n"
|
||||
filename (.getMessage e))))
|
||||
polys (reduce
|
||||
concat
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
"Essentially the specification for things we shall consider to be path.
|
||||
**Note that** for these purposes `path` means any continuous linear
|
||||
feature, where such features specifically include watercourses."
|
||||
(:require [clojure.string :as s]
|
||||
[cc.journeyman.walkmap.edge :as e]
|
||||
[cc.journeyman.walkmap.polygon :refer [check-polygon polygon?]]
|
||||
[cc.journeyman.walkmap.tag :refer [tag tags]]
|
||||
[cc.journeyman.walkmap.utils :refer [check-kind-type check-kind-type-seq kind-type]]
|
||||
(:require [cc.journeyman.walkmap.edge :as e]
|
||||
[cc.journeyman.walkmap.polygon :refer [polygon?]]
|
||||
[cc.journeyman.walkmap.utils :refer [check-kind-type check-kind-type-seq]]
|
||||
[cc.journeyman.walkmap.vertex :refer [check-vertices vertex?]]))
|
||||
|
||||
(defn path?
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
projection of this rectangle on that plane?"
|
||||
[vertex rectangle]
|
||||
(let [xo (sort-by :x (:vertices rectangle))
|
||||
yo (sort-by :x (:vertices rectangle))]
|
||||
yo (sort-by :y (:vertices rectangle))]
|
||||
(and
|
||||
(< (:x (first xo)) (:x vertex) (:x (last xo)))
|
||||
(< (:y (first yo)) (:y vertex) (:y (last yo))))))
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
(ns cc.journeyman.walkmap.read-svg
|
||||
"Utility functions for scalable vector graphics (SVG) into walkmap
|
||||
structures."
|
||||
(:require [clojure.data.zip :as dz]
|
||||
[clojure.data.zip.xml :as zx]
|
||||
[clojure.java.io :as io]
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.string :as s]
|
||||
[clojure.xml :as x]
|
||||
[clojure.zip :as z]
|
||||
[taoensso.timbre :as l]
|
||||
[cc.journeyman.walkmap.path :refer [path]]
|
||||
[cc.journeyman.walkmap.tag :refer [tag]]
|
||||
[cc.journeyman.walkmap.utils :refer [kind-type truncate]]
|
||||
[cc.journeyman.walkmap.vertex :refer [vertex vertex?]]))
|
||||
|
||||
(defn upper-case?
|
||||
|
@ -91,7 +86,7 @@
|
|||
;; quite small XML files. So I've implemented my own solution.
|
||||
([file-name]
|
||||
(read-svg file-name nil))
|
||||
([file-name map-kind]
|
||||
([file-name _map-kind]
|
||||
(let [xml (x/parse (io/file file-name))
|
||||
paths (progeny xml #(= (:tag %) :path))]
|
||||
(remove nil? (map path-elt->path paths)))))
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
(ns cc.journeyman.walkmap.routing
|
||||
"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.string :as cs :only [join]]
|
||||
[search.core :refer [a*]]
|
||||
[cc.journeyman.walkmap.edge :as e]
|
||||
[cc.journeyman.walkmap.path :as p]
|
||||
[cc.journeyman.walkmap.polygon :as q]
|
||||
[cc.journeyman.walkmap.superstructure :as s]
|
||||
[cc.journeyman.walkmap.tag :as t]
|
||||
[cc.journeyman.walkmap.utils :as u]
|
||||
[cc.journeyman.walkmap.vertex :as v]))
|
||||
[cc.journeyman.walkmap.tag :refer [tags]]
|
||||
[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
|
||||
;; equal, but in our world (like the real world), they don't.
|
||||
|
@ -37,25 +36,30 @@
|
|||
of traversals. Dynamic, because we will want to tune this."
|
||||
-2)
|
||||
|
||||
(def ^:dynamic *untraversable*
|
||||
"The set of all tags which may indicate that a polygon should not be
|
||||
traversed."
|
||||
#{:no-traversal})
|
||||
|
||||
(defn traversable?
|
||||
"True if this `object` is a polygon which can be considered as part of
|
||||
the walkmap."
|
||||
[object]
|
||||
(and
|
||||
(q/polygon? object)
|
||||
(:centre object)
|
||||
(not (t/tagged? object :no-traversal))))
|
||||
(q/polygon? object)
|
||||
(:centre object)
|
||||
(empty? (intersection (tags object) *untraversable*))))
|
||||
|
||||
(declare traversal-cost)
|
||||
|
||||
(defn vertices-traversal-cost
|
||||
[vertices s]
|
||||
(reduce
|
||||
+
|
||||
(map
|
||||
#(traversal-cost %1 %2 s)
|
||||
(v/check-vertices vertices)
|
||||
(rest vertices))))
|
||||
+
|
||||
(map
|
||||
#(traversal-cost %1 %2 s)
|
||||
(v/check-vertices vertices)
|
||||
(rest vertices))))
|
||||
|
||||
(defn path-traversal-cost
|
||||
[path s]
|
||||
|
@ -64,7 +68,8 @@
|
|||
(defn barriers-crossed
|
||||
"Search superstructure `s` and return a sequence of barriers, if any, which
|
||||
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
|
||||
'())
|
||||
|
||||
|
@ -74,7 +79,8 @@
|
|||
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
|
||||
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
|
||||
0)
|
||||
|
||||
|
@ -83,7 +89,7 @@
|
|||
[edge]
|
||||
(let [g (:z (e/unit-vector edge))]
|
||||
(if (pos? g)
|
||||
(m/expt (inc g) *gradient-exponent*)
|
||||
(expt (inc g) *gradient-exponent*)
|
||||
1)))
|
||||
|
||||
;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 0)))
|
||||
|
@ -98,10 +104,10 @@
|
|||
[from to s]
|
||||
(let [f (fn [v] (set (s/touching v p/path? s)))]
|
||||
(first
|
||||
(sort-by
|
||||
(sort-by
|
||||
;;; I... chose the path more travelled by.
|
||||
#(or (:traversals %) 0)
|
||||
(filter traversable? (intersection (f from) (f to)))))))
|
||||
#(or (:traversals %) 0)
|
||||
(filter traversable? (intersection (f from) (f to)))))))
|
||||
|
||||
(defn road-bonus
|
||||
"Calculate the road bonus of the edge represented by the vertices `from`,
|
||||
|
@ -112,7 +118,7 @@
|
|||
[from to s]
|
||||
(let [best (best-road from to s)]
|
||||
(when (:traversals best)
|
||||
(m/expt (:traversals best) *traversals-exponent*))))
|
||||
(expt (:traversals best) *traversals-exponent*))))
|
||||
|
||||
(defn traversal-cost
|
||||
"Return the traversal cost of the edge represented by the vertices `from`,
|
||||
|
@ -125,14 +131,14 @@
|
|||
(let [edge (e/edge from to)
|
||||
distance (e/length edge)]
|
||||
(/
|
||||
(+
|
||||
(* distance
|
||||
(gradient-cost edge))
|
||||
(reduce +
|
||||
(map
|
||||
#(crossing-penalty [% from to s])
|
||||
(barriers-crossed from to s))))
|
||||
(or (road-bonus from to s) 1)))))
|
||||
(+
|
||||
(* distance
|
||||
(gradient-cost edge))
|
||||
(reduce +
|
||||
(map
|
||||
#(crossing-penalty % from to s)
|
||||
(barriers-crossed from to s))))
|
||||
(or (road-bonus from to s) 1)))))
|
||||
|
||||
;; (def p '({:x 1.40625, :y 0, :kind :vertex, :walkmap.id/id :vert_1-40625_0}
|
||||
;; {:x 1.40625, :y -10.703125, :kind :vertex, :walkmap.id/id :vert_1-40625_-10-703125}
|
||||
|
@ -154,11 +160,11 @@
|
|||
([frontier candidates]
|
||||
(extend-frontier frontier candidates nil))
|
||||
([frontier candidates rejects]
|
||||
(if
|
||||
(if
|
||||
(empty? frontier)
|
||||
candidates
|
||||
(let [fs (set (concat frontier rejects))]
|
||||
(concat frontier (remove fs candidates))))))
|
||||
candidates
|
||||
(let [fs (set (concat frontier rejects))]
|
||||
(concat frontier (remove fs candidates))))))
|
||||
|
||||
;; (extend-frontier '(1 2 3 4 5) '(7 3 6 2 9 8) '(6 8))
|
||||
;; (extend-frontier '(1 2 3 4 5) '(7 3 6 2 9 8))
|
||||
|
@ -176,11 +182,10 @@
|
|||
[target search-radius s]
|
||||
(let [r (s/nearest s target traversable? search-radius)]
|
||||
(when-not r (throw
|
||||
(Exception.
|
||||
(cs/join " " ["Unable to find walkable facet within"
|
||||
search-radius
|
||||
"of"
|
||||
target]))))
|
||||
(Exception.
|
||||
(format "Unable to find walkable facet within %s of %s"
|
||||
search-radius
|
||||
target))))
|
||||
r))
|
||||
|
||||
(defn route
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
(ns cc.journeyman.walkmap.stl
|
||||
"Utility functions dealing with stereolithography (STL) files. Not a stable API yet!"
|
||||
(:require [clojure.java.io :as io :refer [file output-stream input-stream]]
|
||||
[clojure.string :as s]
|
||||
[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.superstructure :refer [store]]
|
||||
[cc.journeyman.walkmap.tag :refer [tag]]
|
||||
[cc.journeyman.walkmap.utils :as u]
|
||||
[cc.journeyman.walkmap.vertex :as v])
|
||||
(:import org.clojars.smee.binary.core.BinaryIO
|
||||
java.io.DataInput))
|
||||
(:require
|
||||
[cc.journeyman.walkmap.ocean :refer [ocean?]]
|
||||
[cc.journeyman.walkmap.polygon :refer [centre gradient polygon?]]
|
||||
[cc.journeyman.walkmap.superstructure :refer [store]]
|
||||
[cc.journeyman.walkmap.tag :refer [tag]]
|
||||
[cc.journeyman.walkmap.utils :refer [truncate]]
|
||||
[cc.journeyman.walkmap.vertex :as v]
|
||||
[clojure.lang.io :refer [input-stream]]
|
||||
[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?
|
||||
"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
|
||||
`map-kind` is not a keyword."
|
||||
([o] (canonicalise o :height))
|
||||
([o map-kind]
|
||||
([o ^Keyword map-kind]
|
||||
(canonicalise o map-kind (v/vertex 1 1 1)))
|
||||
([o map-kind scale-vertex]
|
||||
([o ^Keyword map-kind scale-vertex]
|
||||
(when-not
|
||||
(keyword? map-kind)
|
||||
(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
|
||||
(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?`
|
||||
|
@ -92,13 +91,13 @@
|
|||
(:vertices o)
|
||||
map-kind))
|
||||
:facet map-kind)))]
|
||||
(if (o/ocean? f)
|
||||
(if (ocean? f)
|
||||
(tag f :ocean :no-traversal)
|
||||
f))
|
||||
;; 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.
|
||||
(:x o) (let [c (v/canonicalise o)]
|
||||
(if scale-vertex
|
||||
(if (v/vertex? scale-vertex)
|
||||
(v/vertex* c scale-vertex)
|
||||
c))
|
||||
;; shouldn't happen
|
||||
|
@ -122,18 +121,14 @@
|
|||
|
||||
**NOTE** that we've no way of verifying that the input file is binary STL
|
||||
data, if it is not this will run but will return garbage."
|
||||
([filename]
|
||||
([^String filename]
|
||||
(decode-binary-stl filename :height))
|
||||
([filename map-kind]
|
||||
(when-not
|
||||
(keyword? map-kind)
|
||||
(throw (IllegalArgumentException.
|
||||
(u/truncate (str "Must be a keyword: " (or map-kind "nil")) 80))))
|
||||
([^String filename ^Keyword map-kind]
|
||||
(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)))
|
||||
([filename map-kind superstructure scale-vertex]
|
||||
(let [in (io/input-stream filename)
|
||||
([^String filename ^Keyword map-kind superstructure scale-vertex]
|
||||
(let [in (input-stream filename)
|
||||
stl (canonicalise (b/decode binary-stl in) map-kind scale-vertex)]
|
||||
(if
|
||||
(map? superstructure)
|
||||
|
@ -175,12 +170,12 @@
|
|||
"Write an `stl` structure as read by `decode-binary-stl` to this
|
||||
`filename` as ASCII encoded STL."
|
||||
([filename stl]
|
||||
(let [b (fs/base-name filename true)]
|
||||
(let [b (base-name filename true)]
|
||||
(write-ascii-stl
|
||||
filename stl
|
||||
(subs b 0 (or (s/index-of b ".") (count b))))))
|
||||
([filename stl solidname]
|
||||
(l/debug "Solid name is " solidname)
|
||||
(debug "Solid name is " solidname)
|
||||
(spit
|
||||
filename
|
||||
(stl->ascii stl solidname))))
|
||||
|
@ -190,7 +185,7 @@
|
|||
`out-filename`, if specified; otherwise, to a file with the same basename
|
||||
as `in-filename` but the extension `.ascii.stl`."
|
||||
([in-filename]
|
||||
(let [[_ ext] (fs/split-ext in-filename)]
|
||||
(let [[_ ext] (split-ext in-filename)]
|
||||
(binary-stl-to-ascii
|
||||
in-filename
|
||||
(str
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
(:require [clojure.walk :refer [postwalk]]
|
||||
[taoensso.timbre :as l]
|
||||
[cc.journeyman.walkmap.edge :refer [edge length]]
|
||||
[cc.journeyman.walkmap.path :as p]
|
||||
[cc.journeyman.walkmap.polygon :as q]
|
||||
;; [cc.journeyman.walkmap.path :as p]
|
||||
;; [cc.journeyman.walkmap.polygon :as q]
|
||||
[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
|
||||
;; some vertices still index it? I *think* that what's needed is that when
|
||||
;; we store something in the superstructure, we replace all its vertices (and
|
||||
;; other dependent structures, if any with their ids - as well as, obviously,
|
||||
;; other dependent structures, if any) with their ids - as well as, obviously,
|
||||
;; adding/merging those vertices/dependent structures into the superstructure
|
||||
;; as first class objects in themselves. That means, for each identified thing,
|
||||
;; the superstructure only contains one copy of it.
|
||||
|
@ -31,14 +32,14 @@
|
|||
[o]
|
||||
(when (map? o)
|
||||
(reduce
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
#(cond
|
||||
(v/vertex? %) (list %)
|
||||
(and (coll? %) (every? v/vertex? %)) %)
|
||||
(vals o))))))
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
#(cond
|
||||
(v/vertex? %) (list %)
|
||||
(and (coll? %) (every? v/vertex? %)) %)
|
||||
(vals o))))))
|
||||
;; (cond
|
||||
;; (v/vertex? o) (list o)
|
||||
;; (q/polygon? o) (:vertices o)
|
||||
|
@ -53,7 +54,7 @@
|
|||
2. `o` is not a map;
|
||||
3. `o` does not have a value for the key `:walkmap.id/id`;
|
||||
4. `v` is not a vertex."
|
||||
[s o v]
|
||||
[^Map s ^Map o ^Map v]
|
||||
(if-not (v/vertex? o)
|
||||
(if (:walkmap.id/id o)
|
||||
(if (v/vertex? v)
|
||||
|
@ -77,14 +78,14 @@
|
|||
3. `o` does not have a value for the key `:walkmap.id/id`."
|
||||
[s o]
|
||||
(u/deep-merge
|
||||
s
|
||||
{::vertex-index
|
||||
(reduce
|
||||
u/deep-merge
|
||||
{}
|
||||
(map
|
||||
#(index-vertex s o %)
|
||||
(:vertices o)))}))
|
||||
s
|
||||
{::vertex-index
|
||||
(reduce
|
||||
u/deep-merge
|
||||
{}
|
||||
(map
|
||||
#(index-vertex s o %)
|
||||
(:vertices o)))}))
|
||||
|
||||
(defn in-retrieve
|
||||
"Internal guts of `retrieve`, q.v. `x` can be anything; `s` must be a
|
||||
|
@ -99,17 +100,17 @@
|
|||
x)
|
||||
;; if it's a map, for every key which is not `:walkmap.id/id`, recurse.
|
||||
(map? x) (let [v (reduce
|
||||
(fn [m k]
|
||||
(assoc m k (in-retrieve (x k) s)))
|
||||
{}
|
||||
(keys (dissoc x :walkmap.id/id)))
|
||||
(fn [m k]
|
||||
(assoc m k (in-retrieve (x k) s)))
|
||||
{}
|
||||
(keys (dissoc x :walkmap.id/id)))
|
||||
id (:walkmap.id/id x)]
|
||||
;; if it has an id, bind it to that id in the returned value.
|
||||
(if id
|
||||
(assoc
|
||||
v
|
||||
:walkmap.id/id
|
||||
(:walkmap.id/id x))
|
||||
v
|
||||
:walkmap.id/id
|
||||
(:walkmap.id/id x))
|
||||
v))
|
||||
(set? x) x ;; TODO: should I search in sets for objects when storing?
|
||||
(coll? x) (map #(in-retrieve % s) x)
|
||||
|
@ -132,9 +133,9 @@
|
|||
(set? o) s ;; TODO: should I search in sets for objects when storing?
|
||||
(map? o) (if (:walkmap.id/id o)
|
||||
(assoc
|
||||
(in-store-find-objects (vals o) s)
|
||||
(:walkmap.id/id o)
|
||||
o)
|
||||
(in-store-find-objects (vals o) s)
|
||||
(:walkmap.id/id o)
|
||||
o)
|
||||
(in-store-find-objects (vals o) s))
|
||||
(coll? o) (reduce merge s (map #(in-store-find-objects % s) o))
|
||||
:else s)))
|
||||
|
@ -145,9 +146,9 @@
|
|||
`in-store`, q.v. Use at your own peril."
|
||||
[o]
|
||||
(assoc
|
||||
(postwalk #(or (:walkmap.id/id %) %) (dissoc o :walkmap.id/id))
|
||||
:walkmap.id/id
|
||||
(:walkmap.id/id o)))
|
||||
(postwalk #(or (:walkmap.id/id %) %) (dissoc o :walkmap.id/id))
|
||||
:walkmap.id/id
|
||||
(:walkmap.id/id o)))
|
||||
|
||||
;; (in-store-replace-with-keys (p/path (v/vertex 0 0 0) (v/vertex 0 1 2) (v/vertex 3 3 3)))
|
||||
;; (in-store-find-objects (p/path (v/vertex 0 0 0) (v/vertex 0 1 2) (v/vertex 3 3 3)))
|
||||
|
@ -161,22 +162,22 @@
|
|||
|
||||
1. `s` is not a map;
|
||||
2. `o` is not a recognisable walkmap object"
|
||||
([o]
|
||||
([^Map o]
|
||||
(store o {}))
|
||||
([o s]
|
||||
([^Map o ^Map s]
|
||||
(when-not (:walkmap.id/id o)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(str "Not a walkmap object: no value for `:walkmap.id/id`: "
|
||||
(u/kind-type o)))))
|
||||
(IllegalArgumentException.
|
||||
(str "Not a walkmap object: no value for `:walkmap.id/id`: "
|
||||
(u/kind-type o)))))
|
||||
(when-not (map? s)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(str "Superstructure must be a map: " (u/kind-type s)))))
|
||||
(IllegalArgumentException.
|
||||
(str "Superstructure must be a map: " (u/kind-type s)))))
|
||||
(assoc
|
||||
(u/deep-merge s (in-store-find-objects o) (index-vertices s o))
|
||||
(:walkmap.id/id o)
|
||||
(in-store-replace-with-keys o))))
|
||||
(u/deep-merge s (in-store-find-objects o) (index-vertices s o))
|
||||
(:walkmap.id/id o)
|
||||
(in-store-replace-with-keys o))))
|
||||
|
||||
(defn search-vertices
|
||||
"Search superstructure `s` for vertices within the box defined by vertices
|
||||
|
@ -188,14 +189,14 @@
|
|||
have properties which will be denormalised by `store`, and therefore do not
|
||||
have to restored with `retrieve`. If properties are added to vertices
|
||||
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))
|
||||
([s minv maxv d2?]
|
||||
(let [minv' (if d2? (assoc minv :z Double/NEGATIVE_INFINITY) minv)
|
||||
maxv' (if d2? (assoc maxv :z Double/POSITIVE_INFINITY) maxv)]
|
||||
(filter
|
||||
#(v/within-box? % minv maxv)
|
||||
(filter #(= (:kind %) :vertex) (vals s))))))
|
||||
#(v/within-box? % minv' maxv')
|
||||
(filter #(= (:kind %) :vertex) (vals s))))))
|
||||
|
||||
(defn nearest
|
||||
"Search superstructure `s` for the nearest object matching `filter-fn` to
|
||||
|
@ -205,68 +206,72 @@
|
|||
|
||||
WARNING: currently only returns objects which have a defined `:centre`
|
||||
(but most of the significant objects we have do)."
|
||||
[s target filter-fn radius]
|
||||
(let [minv (v/vertex
|
||||
([^Map s ^Map target ^Number radius]
|
||||
(nearest s target :centre radius))
|
||||
([^Map s ^Map target ^IFn filter-fn ^Number radius]
|
||||
(let [minv (v/vertex
|
||||
(- (:x (v/check-vertex target)) radius)
|
||||
(- (:y target) radius) (- (or (:z target) 0) radius))
|
||||
maxv (v/vertex
|
||||
maxv (v/vertex
|
||||
(+ (:x target) 0.5) (+ (:y target) 0.5)
|
||||
(+ (or (:z target) 0) 0.5))]
|
||||
;; filter those objects with the filter function, then sort that list
|
||||
;; by the edge distance from the target to the `:centre` of the object
|
||||
;; and take the first
|
||||
(first
|
||||
(first
|
||||
(sort-by
|
||||
#(length (edge target (:centre %)))
|
||||
#(length (edge target (:centre %)))
|
||||
(filter
|
||||
filter-fn
|
||||
(filter
|
||||
:centre
|
||||
(map #(retrieve % s)
|
||||
:centre
|
||||
(map #(retrieve % s)
|
||||
;; for each vertex id in vids, get the objects associated with that id
|
||||
;; in the vertex index as a single flat list
|
||||
(reduce
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
#(-> s ::vertex-index % keys)
|
||||
(reduce
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map
|
||||
#(-> s ::vertex-index % keys)
|
||||
;; get all the vertex ids within radius of the target
|
||||
(set
|
||||
(map
|
||||
:walkmap.id/id
|
||||
(search-vertices s minv maxv))))))))))))
|
||||
(set
|
||||
(map
|
||||
:walkmap.id/id
|
||||
(search-vertices s minv maxv))))))))))))))
|
||||
|
||||
(defn touching
|
||||
"Return a sequence of all objects in superstructure `s` which are
|
||||
indexed as touching the vertex `v`."
|
||||
([vertex s]
|
||||
([^Map vertex ^Map s]
|
||||
(map
|
||||
#(retrieve % s)
|
||||
(set (-> s :vertex-index (:walkmap.id/id (v/check-vertex vertex)) keys))))
|
||||
([vertex filter-fn s]
|
||||
#(retrieve % s)
|
||||
(set (-> s :vertex-index (:walkmap.id/id (v/check-vertex vertex)) keys))))
|
||||
([^Map vertex ^IFn filter-fn ^Map s]
|
||||
(filter
|
||||
filter-fn
|
||||
(touching vertex s))))
|
||||
filter-fn
|
||||
(touching vertex s))))
|
||||
|
||||
(defn neighbours
|
||||
"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`
|
||||
if supplied."
|
||||
([target s]
|
||||
(neighbours identity s))
|
||||
([target filter-fn s]
|
||||
([^Map target ^Map s]
|
||||
(neighbours target identity s))
|
||||
([^Map target ^IFn filter-fn ^Map s]
|
||||
(remove
|
||||
#(= target %)
|
||||
(reduce
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map #(touching % filter-fn s) (vertices target)))))))
|
||||
#(= target %)
|
||||
(reduce
|
||||
concat
|
||||
(remove
|
||||
nil?
|
||||
(map #(touching % filter-fn s) (vertices target)))))))
|
||||
|
||||
(defn neighbour-ids
|
||||
"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
|
||||
`filter-fn` if supplied."
|
||||
([target s]
|
||||
([^Map target ^Map 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))))
|
||||
|
|
|
@ -2,16 +2,12 @@
|
|||
"Utility functions for writing stereolithography (STL) files (and possibly,
|
||||
later, other geometry files of interest to us) as scalable vector graphics
|
||||
(SVG)."
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.string :as s]
|
||||
[clojure.xml :as x]
|
||||
(:require [clojure.string :as s]
|
||||
[dali.io :as neatly-folded-clock]
|
||||
[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.polygon :refer [polygon?]]
|
||||
[cc.journeyman.walkmap.stl :refer [decode-binary-stl]]
|
||||
[cc.journeyman.walkmap.vertex :refer [vertex?]]))
|
||||
[cc.journeyman.walkmap.stl :refer [decode-binary-stl]]))
|
||||
|
||||
(def ^:dynamic *preferred-svg-render*
|
||||
"Mainly for debugging dali; switch SVG renderer to use. Expected values:
|
||||
|
@ -88,7 +84,7 @@
|
|||
(map
|
||||
#(reduce max (map :y (:vertices %)))
|
||||
(:facets stl)))]
|
||||
(l/info "Generating SVG for " *preferred-svg-render* " renderer")
|
||||
(info "Generating SVG for " *preferred-svg-render* " renderer")
|
||||
(case *preferred-svg-render*
|
||||
:hiccup (hiccup-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))))
|
||||
([in-filename out-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*
|
||||
:dali (neatly-folded-clock/render-svg s out-filename)
|
||||
:hiccup (spit out-filename (html s))
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
This is in an attempt to avoid name clashes with other uses of this key."
|
||||
(:require [clojure.set :refer [difference union]]
|
||||
[taoensso.timbre :as l]
|
||||
[cc.journeyman.walkmap.utils :refer [kind-type]]))
|
||||
[cc.journeyman.walkmap.utils :refer [kind-type]])
|
||||
(:import [clojure.lang Map]))
|
||||
|
||||
(defn tagged?
|
||||
"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;
|
||||
2. any of `tags` is not a keyword."
|
||||
[object & tags]
|
||||
(when-not (map? object)
|
||||
(throw (IllegalArgumentException.
|
||||
(str "Must be a map: " (kind-type object)))))
|
||||
[^Map object & tags]
|
||||
(let [tags' (flatten tags)]
|
||||
(when-not (every? keyword? tags')
|
||||
(throw (IllegalArgumentException.
|
||||
|
@ -35,11 +33,8 @@
|
|||
|
||||
It's legal to include sequences of keywords in `tags`, so that users can do
|
||||
useful things like `(tag obj (map keyword some-strings))`."
|
||||
[object & tags]
|
||||
[^Map object & 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)]
|
||||
(when-not (every? keyword? tags')
|
||||
(throw (IllegalArgumentException.
|
||||
|
@ -57,10 +52,7 @@
|
|||
|
||||
1. `object` is not a map;
|
||||
2. any of `tags` is not a keyword or sequence of keywords."
|
||||
[object & tags]
|
||||
(when-not (map? object)
|
||||
(throw (IllegalArgumentException.
|
||||
(str "Must be a map: " (kind-type object)))))
|
||||
[^Map object & tags]
|
||||
(let [tags' (flatten tags)]
|
||||
(when-not (every? keyword? tags')
|
||||
(throw (IllegalArgumentException.
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
(:require [clojure.edn :as edn :only [read]]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.math.numeric-tower :as m]
|
||||
[clojure.set :refer [union]]
|
||||
[clojure.string :as s]))
|
||||
|
||||
(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.
|
||||
;; TODO TODO: if we are to successfully merge walkmap objects, we must
|
||||
;; return, on each object, the union of its tags if any.
|
||||
[& 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)))
|
||||
|
||||
(defn truncate
|
||||
|
@ -27,20 +27,20 @@
|
|||
"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
|
||||
the type of `nil` is `nil`, which doesn't get printed when assembling error
|
||||
,essages, so return \"nil\"."
|
||||
messages, so return \"nil\"."
|
||||
[object]
|
||||
(or (:kind object) (type object) "nil"))
|
||||
|
||||
(defn =ish
|
||||
"True if numbers `n1`, `n2` are roughly equal; that is to say, equal to
|
||||
within `tolerance` (defaults to one part in one hundred thousand)."
|
||||
([n1 n2]
|
||||
([^Number n1 ^Number n2]
|
||||
(if (and (number? n1) (number? n2))
|
||||
(let [m (m/abs (min n1 n2))
|
||||
t (if (zero? m) 0.00001 (* 0.00001 m))]
|
||||
(=ish n1 n2 t))
|
||||
(= n1 n2)))
|
||||
([n1 n2 tolerance]
|
||||
([^Number n1 ^Number n2 ^Number tolerance]
|
||||
(if (and (number? n1) (number? n2))
|
||||
(< (m/abs (- n1 n2)) tolerance)
|
||||
(= n1 n2))))
|
||||
|
@ -55,18 +55,18 @@
|
|||
([object expected]
|
||||
`(if-not (= (kind-type ~object) ~expected)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected" ~expected "but found" (kind-type ~object)])))
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected" ~expected "but found" (kind-type ~object)])))
|
||||
~object))
|
||||
([object checkfn expected]
|
||||
`(if-not (~checkfn ~object)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected" ~expected "but found" (kind-type ~object)])))
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected" ~expected "but found" (kind-type ~object)])))
|
||||
~object)))
|
||||
|
||||
(defmacro check-kind-type-seq
|
||||
|
@ -77,29 +77,29 @@
|
|||
|
||||
Macro, so that the exception is thrown from the calling function."
|
||||
([s expected]
|
||||
`(if-not (every? #(= (kind-type %) ~expected) ~s)
|
||||
(throw
|
||||
`(if-not (every? #(= (kind-type %) ~expected) ~s)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected sequence of"
|
||||
~expected
|
||||
"but found ("
|
||||
(s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
|
||||
")"])))
|
||||
~s))
|
||||
(s/join
|
||||
" "
|
||||
["Expected sequence of"
|
||||
~expected
|
||||
"but found ("
|
||||
(s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
|
||||
")"])))
|
||||
~s))
|
||||
([s checkfn expected]
|
||||
`(if-not (every? #(~checkfn %) ~s)
|
||||
(throw
|
||||
`(if-not (every? #(~checkfn %) ~s)
|
||||
(throw
|
||||
(IllegalArgumentException.
|
||||
(s/join
|
||||
" "
|
||||
["Expected sequence of"
|
||||
~expected
|
||||
"but found ("
|
||||
(s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
|
||||
")"])))
|
||||
~s)))
|
||||
(s/join
|
||||
" "
|
||||
["Expected sequence of"
|
||||
~expected
|
||||
"but found ("
|
||||
(s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
|
||||
")"])))
|
||||
~s)))
|
||||
|
||||
(defn load-edn
|
||||
"Load edn from an io/reader source (filename or io/resource)."
|
||||
|
@ -115,5 +115,5 @@
|
|||
(defn not-yet-implemented
|
||||
[message]
|
||||
(throw
|
||||
(UnsupportedOperationException.
|
||||
(str "Not yet implemented: " message))))
|
||||
(UnsupportedOperationException.
|
||||
(str "Not yet implemented: " message))))
|
||||
|
|
Loading…
Reference in a new issue