150 lines
4.9 KiB
Clojure
150 lines
4.9 KiB
Clojure
(ns walkmap.vertex
|
|
"Essentially the specification for things we shall consider to be vertices.
|
|
|
|
Note that there's no `distance` function here; to find the distance between
|
|
two vertices, create an edge from them and use `walkmap.edge/length`."
|
|
(:require [clojure.math.numeric-tower :as m]
|
|
[clojure.string :as s]
|
|
[taoensso.timbre :as l]
|
|
[walkmap.geometry :refer [=ish]]
|
|
[walkmap.utils :refer [kind-type truncate]]))
|
|
|
|
(defn vertex-key
|
|
"Making sure we get the same key everytime we key a vertex with the same
|
|
coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
|
|
`:z`; it is an error and an exception will be thrown if `o` does not
|
|
conform to this specification.
|
|
|
|
**Note:** these keys can be quite long. No apology is made: it is required
|
|
that the same key can *never* refer to two different locations in space."
|
|
[o]
|
|
(keyword
|
|
(s/replace
|
|
(cond
|
|
(and (:x o) (:y o) (:z o))
|
|
(str "vert_" (:x o) "_" (:y o) "_" (:z o))
|
|
(and (:x o) (:y o))
|
|
(str "vert_" (:x o) "_" (:y o))
|
|
:else
|
|
(throw (IllegalArgumentException.
|
|
(truncate (str "Not a vertex: " (or o "nil")) 80))))
|
|
"."
|
|
"-")))
|
|
|
|
(defn vertex?
|
|
"True if `o` satisfies the conditions for a vertex. That is, essentially,
|
|
that it must rerpresent a two- or three- dimensional vector. A vertex is
|
|
shall be a map having at least the keys `:x` and `:y`, where the value of
|
|
those keys is a number. If the key `:z` is also present, its value must also
|
|
be a number.
|
|
|
|
The name `vector?` was not used as that would clash with a function of that
|
|
name in `clojure.core` whose semantics are entirely different."
|
|
[o]
|
|
(and
|
|
(map? o)
|
|
(:walkmap.id/id o)
|
|
(number? (:x o))
|
|
(number? (:y o))
|
|
(or (nil? (:z o)) (number? (:z o)))
|
|
(or (nil? (:kind o)) (= (:kind o) :vertex))))
|
|
|
|
(defn vertex=
|
|
"True if vertices `v1`, `v2` represent the same vertex."
|
|
[v1 v2]
|
|
(every?
|
|
#(=ish (% v1) (% v2))
|
|
[:x :y :z]))
|
|
|
|
(defn vertex*
|
|
"Return a vertex like `v1`, but with each of its coordinates multiplied
|
|
by the equivalent vertex in `v2`."
|
|
[v1 v2]
|
|
(if
|
|
(and (vertex? v1) (vertex? v2))
|
|
(let [f (fn [v1 v2 coord]
|
|
(* (or (coord v1) 0)
|
|
;; one here is deliberate!
|
|
(or (coord v2) 1)))]
|
|
(assoc v1 :x (f v1 v2 :x)
|
|
:y (f v1 v2 :y)
|
|
:z (f v1 v2 :z)))
|
|
(do (l/warn
|
|
(s/join
|
|
" "
|
|
["in `vertex-multiply`, both must be vectors. v1:" v1 "v2:" v2]))
|
|
v1)))
|
|
|
|
(defn vertex
|
|
"Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
|
|
with those values, plus a unique `:walkmap.id/id` value, and `:kind` set to `:vertex`.
|
|
It's not necessary to use this function to create a vertex, but the `:walkmap.id/id`
|
|
must be present and must be unique."
|
|
([x y]
|
|
(let [v {:x x :y y :kind :vertex}]
|
|
(assoc v :walkmap.id/id (vertex-key v))))
|
|
([x y z]
|
|
(let [v (assoc (vertex x y) :z z)]
|
|
(assoc v :walkmap.id/id (vertex-key v)))))
|
|
|
|
(defn canonicalise
|
|
"If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
|
|
upgrade it to something we will recognise as a vertex."
|
|
[o]
|
|
(if
|
|
(and
|
|
(map? o)
|
|
(number? (:x o))
|
|
(number? (:y o))
|
|
(or (nil? (:z o)) (number? (:z o))))
|
|
(assoc o :kind :vertex :walkmap.id/id (vertex-key o))
|
|
(throw
|
|
(IllegalArgumentException.
|
|
(truncate
|
|
(str "Not a proto-vertex: must have numeric `:x` and `:y`: "
|
|
(or o "nil"))
|
|
80)))))
|
|
|
|
(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."
|
|
(memoize
|
|
(fn
|
|
([o]
|
|
(ensure3d o 0.0))
|
|
([o dflt]
|
|
(cond
|
|
(not (vertex? o)) (throw
|
|
(IllegalArgumentException.
|
|
(truncate (str "Not a vertex: " (or o "nil")) 80)))
|
|
(:z o) o
|
|
:else (assoc o :z dflt))))))
|
|
|
|
(def ensure2d
|
|
"If `o` is a vertex, set its `:z` value to zero; else throw an exception."
|
|
(memoize
|
|
(fn [o]
|
|
(if
|
|
(vertex? o)
|
|
(assoc o :z 0.0)
|
|
(throw
|
|
(IllegalArgumentException.
|
|
(truncate (str "Not a vertex: " (or o "nil")) 80)))))))
|
|
|
|
(defn within-box?
|
|
"True if `target` is within the box defined by `minv` and `maxv`. All
|
|
arguments must be vertices; additionally, both `minv` and `maxv` must
|
|
have `:z` coordinates."
|
|
[target minv maxv]
|
|
(when-not (and (vertex? target) (vertex? minv) (vertex? maxv))
|
|
(throw (IllegalArgumentException.
|
|
(s/join " " ["Arguments to `within-box?` must be vertices:"
|
|
(map kind-type [target minv maxv])]))))
|
|
(every?
|
|
(map
|
|
#(< (% minv) (or (% target) 0) (% maxv))
|
|
[:x :y :z])))
|