rlm@197
|
1 #+title: General Sense/Effector Utilities
|
rlm@151
|
2 #+author: Robert McIntyre
|
rlm@151
|
3 #+email: rlm@mit.edu
|
rlm@151
|
4 #+description: sensory utilities
|
rlm@151
|
5 #+keywords: simulation, jMonkeyEngine3, clojure, simulated senses
|
rlm@151
|
6 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@151
|
7 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@151
|
8
|
rlm@197
|
9 * Namespace/Imports
|
rlm@197
|
10 #+name header
|
rlm@197
|
11 #+begin_src clojure
|
rlm@182
|
12 (ns cortex.sense
|
rlm@183
|
13 "Here are functions useful in the construction of two or more
|
rlm@183
|
14 sensors/effectors."
|
rlm@182
|
15 {:author "Robert McInytre"}
|
rlm@182
|
16 (:use (cortex world util))
|
rlm@182
|
17 (:import ij.process.ImageProcessor)
|
rlm@182
|
18 (:import jme3tools.converters.ImageToAwt)
|
rlm@182
|
19 (:import java.awt.image.BufferedImage)
|
rlm@182
|
20 (:import com.jme3.collision.CollisionResults)
|
rlm@182
|
21 (:import com.jme3.bounding.BoundingBox)
|
rlm@182
|
22 (:import (com.jme3.scene Node Spatial))
|
rlm@182
|
23 (:import com.jme3.scene.control.AbstractControl)
|
rlm@182
|
24 (:import (com.jme3.math Quaternion Vector3f)))
|
rlm@197
|
25 #+end_src
|
rlm@151
|
26
|
rlm@197
|
27 * Blender Utilities
|
rlm@197
|
28 #+name: blender
|
rlm@197
|
29 #+begin_src clojure
|
rlm@181
|
30 (defn meta-data
|
rlm@181
|
31 "Get the meta-data for a node created with blender."
|
rlm@181
|
32 [blender-node key]
|
rlm@151
|
33 (if-let [data (.getUserData blender-node "properties")]
|
rlm@151
|
34 (.findValue data key)
|
rlm@151
|
35 nil))
|
rlm@151
|
36
|
rlm@197
|
37 (defn jme-to-blender
|
rlm@197
|
38 "Convert from JME coordinates to Blender coordinates"
|
rlm@197
|
39 [#^Vector3f in]
|
rlm@197
|
40 (Vector3f. (.getX in)
|
rlm@197
|
41 (- (.getZ in))
|
rlm@197
|
42 (.getY in)))
|
rlm@151
|
43
|
rlm@197
|
44 (defn blender-to-jme
|
rlm@197
|
45 "Convert from Blender coordinates to JME coordinates"
|
rlm@197
|
46 [#^Vector3f in]
|
rlm@197
|
47 (Vector3f. (.getX in)
|
rlm@197
|
48 (.getZ in)
|
rlm@197
|
49 (- (.getY in))))
|
rlm@197
|
50 #+end_src
|
rlm@197
|
51
|
rlm@197
|
52 * Topology Related stuff
|
rlm@197
|
53 #+name: topology
|
rlm@197
|
54 #+begin_src clojure
|
rlm@197
|
55 (defn load-image
|
rlm@197
|
56 "Load an image as a BufferedImage using the asset-manager system."
|
rlm@197
|
57 [asset-relative-path]
|
rlm@197
|
58 (ImageToAwt/convert
|
rlm@197
|
59 (.getImage (.loadTexture (asset-manager) asset-relative-path))
|
rlm@197
|
60 false false 0))
|
rlm@151
|
61
|
rlm@181
|
62 (def white 0xFFFFFF)
|
rlm@181
|
63
|
rlm@181
|
64 (defn white? [rgb]
|
rlm@181
|
65 (= (bit-and white rgb) white))
|
rlm@181
|
66
|
rlm@151
|
67 (defn filter-pixels
|
rlm@151
|
68 "List the coordinates of all pixels matching pred, within the bounds
|
rlm@182
|
69 provided.
|
rlm@182
|
70 bounds -> [x0 y0 width height]"
|
rlm@151
|
71 {:author "Dylan Holmes"}
|
rlm@151
|
72 ([pred #^BufferedImage image]
|
rlm@151
|
73 (filter-pixels pred image [0 0 (.getWidth image) (.getHeight image)]))
|
rlm@151
|
74 ([pred #^BufferedImage image [x0 y0 width height]]
|
rlm@151
|
75 ((fn accumulate [x y matches]
|
rlm@151
|
76 (cond
|
rlm@151
|
77 (>= y (+ height y0)) matches
|
rlm@151
|
78 (>= x (+ width x0)) (recur 0 (inc y) matches)
|
rlm@151
|
79 (pred (.getRGB image x y))
|
rlm@151
|
80 (recur (inc x) y (conj matches [x y]))
|
rlm@151
|
81 :else (recur (inc x) y matches)))
|
rlm@151
|
82 x0 y0 [])))
|
rlm@151
|
83
|
rlm@151
|
84 (defn white-coordinates
|
rlm@151
|
85 "Coordinates of all the white pixels in a subset of the image."
|
rlm@151
|
86 ([#^BufferedImage image bounds]
|
rlm@181
|
87 (filter-pixels white? image bounds))
|
rlm@151
|
88 ([#^BufferedImage image]
|
rlm@181
|
89 (filter-pixels white? image)))
|
rlm@151
|
90
|
rlm@151
|
91 (defn points->image
|
rlm@151
|
92 "Take a sparse collection of points and visuliaze it as a
|
rlm@151
|
93 BufferedImage."
|
rlm@151
|
94 [points]
|
rlm@151
|
95 (if (empty? points)
|
rlm@151
|
96 (BufferedImage. 1 1 BufferedImage/TYPE_BYTE_BINARY)
|
rlm@151
|
97 (let [xs (vec (map first points))
|
rlm@151
|
98 ys (vec (map second points))
|
rlm@151
|
99 x0 (apply min xs)
|
rlm@151
|
100 y0 (apply min ys)
|
rlm@151
|
101 width (- (apply max xs) x0)
|
rlm@151
|
102 height (- (apply max ys) y0)
|
rlm@151
|
103 image (BufferedImage. (inc width) (inc height)
|
rlm@151
|
104 BufferedImage/TYPE_INT_RGB)]
|
rlm@151
|
105 (dorun
|
rlm@151
|
106 (for [x (range (.getWidth image))
|
rlm@151
|
107 y (range (.getHeight image))]
|
rlm@151
|
108 (.setRGB image x y 0xFF0000)))
|
rlm@151
|
109 (dorun
|
rlm@151
|
110 (for [index (range (count points))]
|
rlm@151
|
111 (.setRGB image (- (xs index) x0) (- (ys index) y0) -1)))
|
rlm@151
|
112 image)))
|
rlm@151
|
113
|
rlm@151
|
114 (defn average [coll]
|
rlm@151
|
115 (/ (reduce + coll) (count coll)))
|
rlm@151
|
116
|
rlm@151
|
117 (defn collapse-1d
|
rlm@182
|
118 "One dimensional analogue of collapse."
|
rlm@151
|
119 [center line]
|
rlm@151
|
120 (let [length (count line)
|
rlm@151
|
121 num-above (count (filter (partial < center) line))
|
rlm@151
|
122 num-below (- length num-above)]
|
rlm@151
|
123 (range (- center num-below)
|
rlm@151
|
124 (+ center num-above))))
|
rlm@151
|
125
|
rlm@151
|
126 (defn collapse
|
rlm@151
|
127 "Take a set of pairs of integers and collapse them into a
|
rlm@182
|
128 contigous bitmap with no \"holes\"."
|
rlm@151
|
129 [points]
|
rlm@151
|
130 (if (empty? points) []
|
rlm@151
|
131 (let
|
rlm@151
|
132 [num-points (count points)
|
rlm@151
|
133 center (vector
|
rlm@151
|
134 (int (average (map first points)))
|
rlm@151
|
135 (int (average (map first points))))
|
rlm@151
|
136 flattened
|
rlm@151
|
137 (reduce
|
rlm@151
|
138 concat
|
rlm@151
|
139 (map
|
rlm@151
|
140 (fn [column]
|
rlm@151
|
141 (map vector
|
rlm@151
|
142 (map first column)
|
rlm@151
|
143 (collapse-1d (second center)
|
rlm@151
|
144 (map second column))))
|
rlm@151
|
145 (partition-by first (sort-by first points))))
|
rlm@151
|
146 squeezed
|
rlm@151
|
147 (reduce
|
rlm@151
|
148 concat
|
rlm@151
|
149 (map
|
rlm@151
|
150 (fn [row]
|
rlm@151
|
151 (map vector
|
rlm@151
|
152 (collapse-1d (first center)
|
rlm@151
|
153 (map first row))
|
rlm@151
|
154 (map second row)))
|
rlm@151
|
155 (partition-by second (sort-by second flattened))))
|
rlm@182
|
156 relocated
|
rlm@151
|
157 (let [min-x (apply min (map first squeezed))
|
rlm@151
|
158 min-y (apply min (map second squeezed))]
|
rlm@151
|
159 (map (fn [[x y]]
|
rlm@151
|
160 [(- x min-x)
|
rlm@151
|
161 (- y min-y)])
|
rlm@151
|
162 squeezed))]
|
rlm@182
|
163 relocated)))
|
rlm@151
|
164
|
rlm@197
|
165 #+end_src
|
rlm@197
|
166
|
rlm@197
|
167 * Node level stuff
|
rlm@197
|
168 #+name: node
|
rlm@197
|
169 #+begin_src clojure
|
rlm@197
|
170 (defn closest-node
|
rlm@197
|
171 "Return the node in creature which is closest to the given node."
|
rlm@197
|
172 [#^Node creature #^Node eye]
|
rlm@197
|
173 (loop [radius (float 0.01)]
|
rlm@197
|
174 (let [results (CollisionResults.)]
|
rlm@197
|
175 (.collideWith
|
rlm@197
|
176 creature
|
rlm@197
|
177 (BoundingBox. (.getWorldTranslation eye)
|
rlm@197
|
178 radius radius radius)
|
rlm@197
|
179 results)
|
rlm@197
|
180 (if-let [target (first results)]
|
rlm@197
|
181 (.getGeometry target)
|
rlm@197
|
182 (recur (float (* 2 radius)))))))
|
rlm@197
|
183
|
rlm@197
|
184 (defn bind-sense
|
rlm@197
|
185 "Bind the sense to the Spatial such that it will maintain its
|
rlm@197
|
186 current position relative to the Spatial no matter how the spatial
|
rlm@197
|
187 moves. 'sense can be either a Camera or Listener object."
|
rlm@197
|
188 [#^Spatial obj sense]
|
rlm@197
|
189 (let [sense-offset (.subtract (.getLocation sense)
|
rlm@197
|
190 (.getWorldTranslation obj))
|
rlm@197
|
191 initial-sense-rotation (Quaternion. (.getRotation sense))
|
rlm@197
|
192 base-anti-rotation (.inverse (.getWorldRotation obj))]
|
rlm@197
|
193 (.addControl
|
rlm@197
|
194 obj
|
rlm@197
|
195 (proxy [AbstractControl] []
|
rlm@197
|
196 (controlUpdate [tpf]
|
rlm@197
|
197 (let [total-rotation
|
rlm@197
|
198 (.mult base-anti-rotation (.getWorldRotation obj))]
|
rlm@197
|
199 (.setLocation
|
rlm@197
|
200 sense
|
rlm@197
|
201 (.add
|
rlm@197
|
202 (.mult total-rotation sense-offset)
|
rlm@197
|
203 (.getWorldTranslation obj)))
|
rlm@197
|
204 (.setRotation
|
rlm@197
|
205 sense
|
rlm@197
|
206 (.mult total-rotation initial-sense-rotation))))
|
rlm@197
|
207 (controlRender [_ _])))))
|
rlm@197
|
208
|
rlm@156
|
209 (defn world-to-local
|
rlm@182
|
210 "Convert the world coordinates into coordinates relative to the
|
rlm@156
|
211 object (i.e. local coordinates), taking into account the rotation
|
rlm@156
|
212 of object."
|
rlm@182
|
213 [#^Spatial object world-coordinate]
|
rlm@182
|
214 (.worldToLocal object world-coordinate nil))
|
rlm@156
|
215
|
rlm@156
|
216 (defn local-to-world
|
rlm@182
|
217 "Convert the local coordinates into world relative coordinates"
|
rlm@182
|
218 [#^Spatial object local-coordinate]
|
rlm@182
|
219 (.localToWorld object local-coordinate nil))
|
rlm@156
|
220
|
rlm@182
|
221 (defn sense-nodes
|
rlm@182
|
222 "For each sense there is a special blender node whose children are
|
rlm@182
|
223 considered markers for an instance of a that sense. This function
|
rlm@182
|
224 generates functions to find those children, given the name of the
|
rlm@182
|
225 special parent node."
|
rlm@182
|
226 [parent-name]
|
rlm@164
|
227 (fn [#^Node creature]
|
rlm@164
|
228 (if-let [sense-node (.getChild creature parent-name)]
|
rlm@164
|
229 (seq (.getChildren sense-node))
|
rlm@164
|
230 (do (println-repl "could not find" parent-name "node") []))))
|
rlm@197
|
231 #+end_src
|
rlm@164
|
232
|
rlm@197
|
233 * Viewing Senses
|
rlm@197
|
234 #+name view-senses
|
rlm@197
|
235 #+begin_src clojure
|
rlm@187
|
236 (defn view-sense
|
rlm@197
|
237 "Take a kernel that produces a BufferedImage from some sense data
|
rlm@197
|
238 and return a function which takes a list of sense data, uses the
|
rlm@197
|
239 kernem to convert to images, and displays those images, each in
|
rlm@197
|
240 its own JFrame."
|
rlm@187
|
241 [sense-display-kernel]
|
rlm@197
|
242 (let [windows (atom [])]
|
rlm@187
|
243 (fn [data]
|
rlm@187
|
244 (if (> (count data) (count @windows))
|
rlm@197
|
245 (reset!
|
rlm@197
|
246 windows (map (fn [_] (view-image)) (range (count data)))))
|
rlm@187
|
247 (dorun
|
rlm@187
|
248 (map
|
rlm@188
|
249 (fn [display datum]
|
rlm@188
|
250 (display (sense-display-kernel datum)))
|
rlm@188
|
251 @windows data)))))
|
rlm@187
|
252
|
rlm@188
|
253 (defn gray
|
rlm@197
|
254 "Create a gray RGB pixel with R, G, and B set to num. num must be
|
rlm@197
|
255 between 0 and 255."
|
rlm@188
|
256 [num]
|
rlm@188
|
257 (+ num
|
rlm@188
|
258 (bit-shift-left num 8)
|
rlm@188
|
259 (bit-shift-left num 16)))
|
rlm@188
|
260 #+end_src
|
rlm@187
|
261
|
rlm@151
|
262 * COMMENT generate source
|
rlm@151
|
263 #+begin_src clojure :tangle ../src/cortex/sense.clj
|
rlm@197
|
264 <<header>>
|
rlm@197
|
265 <<blender>>
|
rlm@197
|
266 <<topology>>
|
rlm@197
|
267 <<node>>
|
rlm@197
|
268 <<view-senses>>
|
rlm@151
|
269 #+end_src
|