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