#3: Massive changes, superstructure now kind-of works

More unit tests now, and they all pass. More work needed, but this is very promising.
This commit is contained in:
Simon Brooke 2020-05-25 23:55:52 +01:00
parent f49a7495db
commit f616992191
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
33 changed files with 1768 additions and 672 deletions

View file

@ -1,52 +0,0 @@
# -*- encoding: utf-8 -*-
# From https://github.com/IsseiMori/binary-stl-toASCII/blob/master/BinaryToASCII.py
# Included here to sanity check.
import struct
infile = open('../the-great-game/resources/maps/heightmap.stl') #import file
out = open('ASCII.stl', 'w') #export file
data = infile.read()
out.write("solid ")
for x in xrange(0,80):
if not ord(data[x]) == 0:
out.write(struct.unpack('c', data[x])[0])
else:
pass
out.write("\n")
number = data[80] + data[81] + data[82] + data[83]
faces = struct.unpack('I',number)[0]
for x in range(0,faces):
out.write("facet normal ")
xc = data[84+x*50] + data[85+x*50] + data[86+x*50] + data[87+x*50]
yc = data[88+x*50] + data[89+x*50] + data[90+x*50] + data[91+x*50]
zc = data[92+x*50] + data[93+x*50] + data[94+x*50] + data[95+x*50]
out.write(str(struct.unpack('f',xc)[0]) + " ")
out.write(str(struct.unpack('f',yc)[0]) + " ")
out.write(str(struct.unpack('f',zc)[0]) + "\n")
out.write("outer loop\n")
for y in range(1,4):
out.write("vertex ")
xc = data[84+y*12+x*50] + data[85+y*12+x*50] + data[86+y*12+x*50] + data[87+y*12+x*50]
yc = data[88+y*12+x*50] + data[89+y*12+x*50] + data[90+y*12+x*50] + data[91+y*12+x*50]
zc = data[92+y*12+x*50] + data[93+y*12+x*50] + data[94+y*12+x*50] + data[95+y*12+x*50]
out.write(str(struct.unpack('f',xc)[0]) + " ")
out.write(str(struct.unpack('f',yc)[0]) + " ")
out.write(str(struct.unpack('f',zc)[0]) + "\n")
out.write("endloop\n")
out.write("endfacet\n")
out.close()
print "end"

View file

@ -13,7 +13,15 @@
(and
(seq? v)
(> (count v) 2)
(every? vertex? v))))
(every? vertex? v)
(or (nil? (:kind o)) (= (:kind o) :path)))))
(defn make-path
[nodes]
(if
(every? vertex? nodes)
{:nodes nodes :id (keyword (gensym "path")) :kind :path}
(throw (Exception. "Each item on path must be a vertex."))))
(defn polygon->path
"If `o` is a polygon, return an equivalent path. What's different about
@ -25,6 +33,6 @@
[o]
(if
(polygon? o)
(assoc (dissoc o :vertices) :nodes (concat (:vertices o) (list (first (:vertices o)))))
(assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o)))))
(throw (Exception. "Not a polygon!"))))

View file

@ -10,8 +10,9 @@
(let
[v (:vertices o)]
(and
(seq? v)
(coll? v)
(> (count v) 2)
(every? vertex? v))))
(every? vertex? v)
(or (nil? (:kind o)) (= (:kind o) :polygon)))))

View file

@ -5,7 +5,8 @@
[me.raynes.fs :as fs]
[org.clojars.smee.binary.core :as b]
[taoensso.timbre :as l :refer [info error spy]]
[walkmap.polygon :refer [polygon?]])
[walkmap.polygon :refer [polygon?]]
[walkmap.vertex :refer [vertex-key]])
(:import org.clojars.smee.binary.core.BinaryIO
java.io.DataInput))
@ -26,6 +27,7 @@
(every? polygon? (:facets o))
(if (:header o) (string? (:header o)) true)
(if (:count o) (integer? (:count o)) true)
(or (nil? (:kind o)) (= (:kind o) :stl))
(if verify-count? (= (:count o) (count (:facets o))) true))))
(def vect
@ -49,6 +51,26 @@
:count :uint-le
:facets (b/repeated facet)))
(defn canonicalise
"Objects read in from STL won't have all the keys/values we need them to have."
[o]
(cond
(and (coll? o) (not (map? o))) (map canonicalise o)
;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?`
(:facets o) (assoc o
:kind :stl
:id (or (:id o) (keyword (gensym "stl")))
:facets (canonicalise (:facets o)))
;; if it has :vertices it's a polygon, but it doesn't yet conform to `polygon?`
(:vertices o) (assoc o
:id (or (:id o) (keyword (gensym "poly")))
:kind :polygon
:vertices (canonicalise (:vertices o)))
;; if it has a value for :x it's a vertex, but it doesn't yet conform to `vertex?`
(:x o) (assoc o :kind :vertex :id (or (:id o) (vertex-key o)))
;; shouldn't happen
:else o))
(defn decode-binary-stl
"Parse a binary STL file from this `filename` and return an STL structure
representing its contents.
@ -57,7 +79,7 @@
data, if it is not this will run but will return garbage."
[filename]
(let [in (io/input-stream filename)]
(b/decode binary-stl in)))
(canonicalise (b/decode binary-stl in))))
(defn- vect->str [prefix v]
(str prefix " " (:x v) " " (:y v) " " (:z v) "\n"))

View file

@ -0,0 +1,76 @@
(ns walkmap.superstructure
"single indexing structure for walkmap objects"
(:require [walkmap.path :as p]
[walkmap.polygon :as q]
[walkmap.stl :as s]
[walkmap.utils :as u]
[walkmap.vertex :as v]))
(defn index-vertex
"Return a superstructure like `s` in which object `o` is indexed by vertex
`v`. It is an error (and an exception may be thrown) if
1. `s` is not a map;
2. `o` is not a map;
3. `o` does not have a value for the key `:id`;
4. `v` is not a vertex."
;; two copies of the same vertex are not identical enough to one another
;; to be used as keys in a map. So our vertices need to have ids, and we need
;; to key the vertex-index by vertex ids.
;; TODO: BUT WE CANNOT USE GENSYMED ids, because two vertices with the same
;; vertices must have the same id!
[s o v]
(if-not (v/vertex? o)
(if (:id o)
(if (v/vertex? v)
(let [vi (or (:vertex-index s) {})
current (or (vi (:id v)) {})]
;; deep-merge doesn't merge sets, only maps; so at this
;; stage we need to build a map.
(assoc vi (:id v) (assoc current (:id o) (:id v))))
(throw (Exception. "Not a vertex: " v)))
(throw (Exception. (subs (str "No `:id` value: " o) 0 80))))
;; it shouldn't actually be an error to try to index a vertex, but it
;; also isn't useful to do so, so I'd be inclined to ignore it.
(:vertex-index s)))
(defn index-vertices
"Return a superstructure like `s` in which object `o` is indexed by its
vertices. It is an error (and an exception may be thrown) if
1. `s` is not a map;
2. `o` is not a map;
3. `o` does not have a value for the key `:id`."
[s o]
(assoc
s
:vertex-index
(reduce
u/deep-merge
(map
#(index-vertex s o %)
(u/vertices o)))))
(defn add-to-superstructure
"Return a superstructure like `s` with object `o` added. If `o` is a collection,
return a superstructure like `s` with each element of `o` added. If only one
argument is supplied it will be assumed to represent `o` and a new
superstructure will be returned.
It is an error (and an exception may be thrown) if
1. `s` is not a map;
2. `o` is not a map, or a sequence of maps."
([o]
(add-to-superstructure {} o))
([s o]
(cond
(map? o) (let [o' (if (:id o) o (assoc o :id (keyword (gensym "obj"))))]
(index-vertices (assoc s (:id o') o') o'))
(coll? o) (reduce u/deep-merge (map #(add-to-superstructure s %) o))
(nil? o) o
:else
(throw (Exception. (str "Don't know how to index " (or (type o) "nil")))))))
(:vertex-index (add-to-superstructure (:facets (s/decode-binary-stl "resources/isle_of_man.stl"))))
(s/decode-binary-stl "resources/isle_of_man.stl")

26
src/walkmap/utils.clj Normal file
View file

@ -0,0 +1,26 @@
(ns walkmap.utils
"Miscellaneous utility functions."
(:require [walkmap.path :as p]
[walkmap.polygon :as q]
[walkmap.vertex :as v]))
(defn deep-merge
"Recursively merges maps. If vals are not maps, the last value wins."
;; TODO: not my implementation, not sure I entirely trust it.
[& vals]
(if (every? map? vals)
(apply merge-with deep-merge vals)
(last vals)))
(defn vertices
"If `o` is an object with vertices, return those vertices, else nil."
;; TODO: it's possibly a design mistake that I'm currently distinguishing
;; between polygons and paths on the basis that one has `:vertices` and
;; the other has `:nodes`. Possibly it would be better to have a key
;; `:closed` which was `true` for polygons, `false` (or missing) for
;; paths.
[o]
(cond
(v/vertex? o) (list o)
(q/polygon? o) (:vertices o)
(p/path? o) (:nodes o)))

View file

@ -1,6 +1,16 @@
(ns walkmap.vertex
"Essentially the specification for things we shall consider to be vertices.")
(defn vertex-key
"Making sure we get the same key everytime we key a vertex with the same
coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
`:z`."
[o]
(cond
(and (:x o) (:y o) (:z o)) (keyword (str "vert{" (:x o) "|" (:y o) "|" (:z o) "}"))
(and (:x o) (:y o)) (keyword (str "vert{" (:x o) "|" (:y o) "}"))
:else (throw (Exception. "Not a vertex."))))
(defn vertex?
"True if `o` satisfies the conditions for a vertex. That is, essentially,
that it must rerpresent a two- or three- dimensional vector. A vertex is
@ -13,9 +23,22 @@
[o]
(and
(map? o)
(:id o)
(number? (:x o))
(number? (:y o))
(or (nil? (:z o)) (number? (:z o)))))
(or (nil? (:z o)) (number? (:z o)))
(or (nil? (:kind o)) (= (:kind o) :vertex))))
(defn make-vertex
"Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
with those values, plus a unique `:id` value, and `:kind` set to `:vertex`.
It's not necessary to use this function to create a vertex, but the `:id`
must be present and must be unique."
([x y]
(let [v {:x x :y y :kind :vertex}]
(assoc v :id (vertex-key v))))
([x y z]
(assoc (make-vertex x y) :z z)))
(def ensure3d
"Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise