rlm@37
|
1 #+title: Simulated Sense of Touch
|
rlm@0
|
2 #+author: Robert McIntyre
|
rlm@0
|
3 #+email: rlm@mit.edu
|
rlm@37
|
4 #+description: Simulated touch for AI research using JMonkeyEngine and clojure.
|
rlm@37
|
5 #+keywords: simulation, tactile sense, jMonkeyEngine3, clojure
|
rlm@4
|
6 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@4
|
7 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@0
|
8
|
rlm@37
|
9 * Touch
|
rlm@0
|
10
|
rlm@226
|
11 Touch is critical to navigation and spatial reasoning and as such I
|
rlm@226
|
12 need a simulated version of it to give to my AI creatures.
|
rlm@0
|
13
|
rlm@228
|
14 However, touch in my virtual can not exactly correspond to human touch
|
rlm@228
|
15 because my creatures are made out of completely rigid segments that
|
rlm@228
|
16 don't deform like human skin.
|
rlm@228
|
17
|
rlm@228
|
18 Human skin has a wide array of touch sensors, each of which speciliaze
|
rlm@228
|
19 in detecting different vibrational modes and pressures. These sensors
|
rlm@228
|
20 can integrate a vast expanse of skin (i.e. your entire palm), or a
|
rlm@228
|
21 tiny patch of skin at the tip of your finger. The hairs of the skin
|
rlm@228
|
22 help detect objects before they even come into contact with the skin
|
rlm@228
|
23 proper.
|
rlm@228
|
24
|
rlm@228
|
25 Instead of measuring deformation or vibration, I surround each rigid
|
rlm@228
|
26 part with a plenitude of hair-like objects which do not interact with
|
rlm@228
|
27 the physical world. Physical objects can pass through them with no
|
rlm@228
|
28 effect. The hairs are able to measure contact with other objects, and
|
rlm@228
|
29 constantly report how much of their extent is covered. So, even though
|
rlm@228
|
30 the creature's body parts do not deform, the hairs create a margin
|
rlm@228
|
31 around those body parts which achieves a sense of touch which is a
|
rlm@228
|
32 hybrid between a human's sense of deformation and sense from hairs.
|
rlm@228
|
33
|
rlm@228
|
34 Implementing touch in jMonkeyEngine follows a different techinal route
|
rlm@228
|
35 than vision and hearing. Those two senses piggybacked off
|
rlm@228
|
36 jMonkeyEngine's 3D audio and video rendering subsystems. To simulate
|
rlm@228
|
37 Touch, I use jMonkeyEngine's physics system to execute many small
|
rlm@228
|
38 collision detections, one for each "hair".
|
rlm@228
|
39
|
rlm@228
|
40 * Sensor Related Functions
|
rlm@0
|
41 #+begin_src clojure
|
rlm@156
|
42 (defn sensors-in-triangle
|
rlm@178
|
43 "Locate the touch sensors in the triangle, returning a map of their
|
rlm@178
|
44 UV and geometry-relative coordinates."
|
rlm@156
|
45 [image mesh tri-index]
|
rlm@156
|
46 (let [width (.getWidth image)
|
rlm@156
|
47 height (.getHeight image)
|
rlm@156
|
48 UV-vertex-coords (triangle-UV-coord mesh width height tri-index)
|
rlm@156
|
49 bounds (convex-bounds UV-vertex-coords)
|
rlm@156
|
50
|
rlm@156
|
51 cutout-triangle (points->triangle UV-vertex-coords)
|
rlm@156
|
52 UV-sensor-coords
|
rlm@156
|
53 (filter (comp (partial inside-triangle? cutout-triangle)
|
rlm@156
|
54 (fn [[u v]] (Vector3f. u v 0)))
|
rlm@156
|
55 (white-coordinates image bounds))
|
rlm@156
|
56 UV->geometry (triangle-transformation
|
rlm@156
|
57 cutout-triangle
|
rlm@227
|
58 (mesh-triangle mesh tri-index))
|
rlm@156
|
59 geometry-sensor-coords
|
rlm@156
|
60 (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0)))
|
rlm@156
|
61 UV-sensor-coords)]
|
rlm@156
|
62 {:UV UV-sensor-coords :geometry geometry-sensor-coords}))
|
rlm@156
|
63
|
rlm@156
|
64 (defn-memo locate-feelers
|
rlm@178
|
65 "Search the geometry's tactile UV profile for touch sensors,
|
rlm@178
|
66 returning their positions in geometry-relative coordinates."
|
rlm@156
|
67 [#^Geometry geo]
|
rlm@156
|
68 (let [mesh (.getMesh geo)
|
rlm@156
|
69 num-triangles (.getTriangleCount mesh)]
|
rlm@178
|
70 (if-let [image (tactile-sensor-profile geo)]
|
rlm@156
|
71 (map
|
rlm@156
|
72 (partial sensors-in-triangle image mesh)
|
rlm@156
|
73 (range num-triangles))
|
rlm@156
|
74 (repeat (.getTriangleCount mesh) {:UV nil :geometry nil}))))
|
rlm@156
|
75
|
rlm@178
|
76 (defn-memo touch-topology
|
rlm@178
|
77 "Return a sequence of vectors of the form [x y] describing the
|
rlm@178
|
78 \"topology\" of the tactile sensors. Points that are close together
|
rlm@178
|
79 in the touch-topology are generally close together in the simulation."
|
rlm@178
|
80 [#^Gemoetry geo]
|
rlm@156
|
81 (vec (collapse (reduce concat (map :UV (locate-feelers geo))))))
|
rlm@156
|
82
|
rlm@178
|
83 (defn-memo feeler-coordinates
|
rlm@178
|
84 "The location of the touch sensors in world-space coordinates."
|
rlm@178
|
85 [#^Geometry geo]
|
rlm@156
|
86 (vec (map :geometry (locate-feelers geo))))
|
rlm@228
|
87 #+end_src
|
rlm@156
|
88
|
rlm@228
|
89 * Triangle Manipulation Functions
|
rlm@228
|
90
|
rlm@228
|
91 #+begin_src clojure
|
rlm@228
|
92 (defn triangles
|
rlm@228
|
93 "Return a sequence of all the Triangles which compose a given
|
rlm@228
|
94 Geometry."
|
rlm@228
|
95 [#^Geometry geom]
|
rlm@228
|
96 (let
|
rlm@228
|
97 [mesh (.getMesh geom)
|
rlm@228
|
98 triangles (transient [])]
|
rlm@228
|
99 (dorun
|
rlm@228
|
100 (for [n (range (.getTriangleCount mesh))]
|
rlm@228
|
101 (let [tri (Triangle.)]
|
rlm@228
|
102 (.getTriangle mesh n tri)
|
rlm@228
|
103 ;; (.calculateNormal tri)
|
rlm@228
|
104 ;; (.calculateCenter tri)
|
rlm@228
|
105 (conj! triangles tri))))
|
rlm@228
|
106 (persistent! triangles)))
|
rlm@228
|
107
|
rlm@228
|
108 (defn mesh-triangle
|
rlm@228
|
109 "Get the triangle specified by triangle-index from the mesh within
|
rlm@228
|
110 bounds."
|
rlm@228
|
111 [#^Mesh mesh triangle-index]
|
rlm@228
|
112 (let [scratch (Triangle.)]
|
rlm@228
|
113 (.getTriangle mesh triangle-index scratch)
|
rlm@228
|
114 scratch))
|
rlm@228
|
115
|
rlm@228
|
116 (defn triangle-vertex-indices
|
rlm@228
|
117 "Get the triangle vertex indices of a given triangle from a given
|
rlm@228
|
118 mesh."
|
rlm@228
|
119 [#^Mesh mesh triangle-index]
|
rlm@228
|
120 (let [indices (int-array 3)]
|
rlm@228
|
121 (.getTriangle mesh triangle-index indices)
|
rlm@228
|
122 (vec indices)))
|
rlm@228
|
123
|
rlm@228
|
124 (defn vertex-UV-coord
|
rlm@228
|
125 "Get the UV-coordinates of the vertex named by vertex-index"
|
rlm@228
|
126 [#^Mesh mesh vertex-index]
|
rlm@228
|
127 (let [UV-buffer
|
rlm@228
|
128 (.getData
|
rlm@228
|
129 (.getBuffer
|
rlm@228
|
130 mesh
|
rlm@228
|
131 VertexBuffer$Type/TexCoord))]
|
rlm@228
|
132 [(.get UV-buffer (* vertex-index 2))
|
rlm@228
|
133 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
|
rlm@228
|
134
|
rlm@228
|
135 (defn triangle-UV-coord
|
rlm@228
|
136 "Get the UV-cooridnates of the triangle's verticies."
|
rlm@228
|
137 [#^Mesh mesh width height triangle-index]
|
rlm@228
|
138 (map (fn [[u v]] (vector (* width u) (* height v)))
|
rlm@228
|
139 (map (partial vertex-UV-coord mesh)
|
rlm@228
|
140 (triangle-vertex-indices mesh triangle-index))))
|
rlm@228
|
141 #+end_src
|
rlm@228
|
142
|
rlm@228
|
143 * Schrapnel Conversion Functions
|
rlm@228
|
144 #+begin_src clojure
|
rlm@228
|
145 (defn triangle-seq [#^Triangle tri]
|
rlm@228
|
146 [(.get1 tri) (.get2 tri) (.get3 tri)])
|
rlm@228
|
147
|
rlm@228
|
148 (defn vector3f-seq [#^Vector3f v]
|
rlm@228
|
149 [(.getX v) (.getY v) (.getZ v)])
|
rlm@228
|
150
|
rlm@228
|
151 (defn point->vector2f [[u v]]
|
rlm@228
|
152 (Vector2f. u v))
|
rlm@228
|
153
|
rlm@228
|
154 (defn vector2f->vector3f [v]
|
rlm@228
|
155 (Vector3f. (.getX v) (.getY v) 0))
|
rlm@228
|
156
|
rlm@228
|
157 (defn map-triangle [f #^Triangle tri]
|
rlm@228
|
158 (Triangle.
|
rlm@228
|
159 (f 0 (.get1 tri))
|
rlm@228
|
160 (f 1 (.get2 tri))
|
rlm@228
|
161 (f 2 (.get3 tri))))
|
rlm@228
|
162
|
rlm@228
|
163 (defn points->triangle
|
rlm@228
|
164 "Convert a list of points into a triangle."
|
rlm@228
|
165 [points]
|
rlm@228
|
166 (apply #(Triangle. %1 %2 %3)
|
rlm@228
|
167 (map (fn [point]
|
rlm@228
|
168 (let [point (vec point)]
|
rlm@228
|
169 (Vector3f. (get point 0 0)
|
rlm@228
|
170 (get point 1 0)
|
rlm@228
|
171 (get point 2 0))))
|
rlm@228
|
172 (take 3 points))))
|
rlm@228
|
173 #+end_src
|
rlm@228
|
174
|
rlm@228
|
175 * Triangle Affine Transforms
|
rlm@228
|
176
|
rlm@228
|
177 #+begin_src clojure
|
rlm@228
|
178 (defn triangle->matrix4f
|
rlm@228
|
179 "Converts the triangle into a 4x4 matrix: The first three columns
|
rlm@228
|
180 contain the vertices of the triangle; the last contains the unit
|
rlm@228
|
181 normal of the triangle. The bottom row is filled with 1s."
|
rlm@228
|
182 [#^Triangle t]
|
rlm@228
|
183 (let [mat (Matrix4f.)
|
rlm@228
|
184 [vert-1 vert-2 vert-3]
|
rlm@228
|
185 ((comp vec map) #(.get t %) (range 3))
|
rlm@228
|
186 unit-normal (do (.calculateNormal t)(.getNormal t))
|
rlm@228
|
187 vertices [vert-1 vert-2 vert-3 unit-normal]]
|
rlm@228
|
188 (dorun
|
rlm@228
|
189 (for [row (range 4) col (range 3)]
|
rlm@228
|
190 (do
|
rlm@228
|
191 (.set mat col row (.get (vertices row)col))
|
rlm@228
|
192 (.set mat 3 row 1))))
|
rlm@228
|
193 mat))
|
rlm@228
|
194
|
rlm@228
|
195 (defn triangle-transformation
|
rlm@228
|
196 "Returns the affine transformation that converts each vertex in the
|
rlm@228
|
197 first triangle into the corresponding vertex in the second
|
rlm@228
|
198 triangle."
|
rlm@228
|
199 [#^Triangle tri-1 #^Triangle tri-2]
|
rlm@228
|
200 (.mult
|
rlm@228
|
201 (triangle->matrix4f tri-2)
|
rlm@228
|
202 (.invert (triangle->matrix4f tri-1))))
|
rlm@228
|
203 #+end_src
|
rlm@228
|
204
|
rlm@228
|
205 * Blender Meta-Data
|
rlm@228
|
206
|
rlm@228
|
207 #+begin_src clojure
|
rlm@228
|
208 (defn tactile-sensor-profile
|
rlm@228
|
209 "Return the touch-sensor distribution image in BufferedImage format,
|
rlm@228
|
210 or nil if it does not exist."
|
rlm@228
|
211 [#^Geometry obj]
|
rlm@228
|
212 (if-let [image-path (meta-data obj "touch")]
|
rlm@228
|
213 (load-image image-path)))
|
rlm@228
|
214 #+end_src
|
rlm@228
|
215
|
rlm@228
|
216 * Physics Collision Objects
|
rlm@228
|
217 #+begin_src clojure
|
rlm@228
|
218 (defn get-ray-origin
|
rlm@228
|
219 "Return the origin which a Ray would have to have to be in the exact
|
rlm@228
|
220 center of a particular Triangle in the Geometry in World
|
rlm@228
|
221 Coordinates."
|
rlm@228
|
222 [geom tri]
|
rlm@228
|
223 (let [new (Vector3f.)]
|
rlm@228
|
224 (.calculateCenter tri)
|
rlm@228
|
225 (.localToWorld geom (.getCenter tri) new) new))
|
rlm@228
|
226
|
rlm@228
|
227 (defn get-ray-direction
|
rlm@228
|
228 "Return the direction which a Ray would have to have to be to point
|
rlm@228
|
229 normal to the Triangle, in coordinates relative to the center of the
|
rlm@228
|
230 Triangle."
|
rlm@228
|
231 [geom tri]
|
rlm@228
|
232 (let [n+c (Vector3f.)]
|
rlm@228
|
233 (.calculateNormal tri)
|
rlm@228
|
234 (.calculateCenter tri)
|
rlm@228
|
235 (.localToWorld
|
rlm@228
|
236 geom
|
rlm@228
|
237 (.add (.getCenter tri) (.getNormal tri)) n+c)
|
rlm@228
|
238 (.subtract n+c (get-ray-origin geom tri))))
|
rlm@228
|
239 #+end_src
|
rlm@228
|
240
|
rlm@228
|
241 * Triangle Boundaries
|
rlm@228
|
242 #+begin_src clojure
|
rlm@228
|
243 (defn same-side?
|
rlm@228
|
244 "Given the points p1 and p2 and the reference point ref, is point p
|
rlm@228
|
245 on the same side of the line that goes through p1 and p2 as ref is?"
|
rlm@228
|
246 [p1 p2 ref p]
|
rlm@228
|
247 (<=
|
rlm@228
|
248 0
|
rlm@228
|
249 (.dot
|
rlm@228
|
250 (.cross (.subtract p2 p1) (.subtract p p1))
|
rlm@228
|
251 (.cross (.subtract p2 p1) (.subtract ref p1)))))
|
rlm@228
|
252
|
rlm@228
|
253 (defn inside-triangle?
|
rlm@228
|
254 "Is the point inside the triangle?"
|
rlm@228
|
255 {:author "Dylan Holmes"}
|
rlm@228
|
256 [#^Triangle tri #^Vector3f p]
|
rlm@228
|
257 (let [[vert-1 vert-2 vert-3] (triangle-seq tri)]
|
rlm@228
|
258 (and
|
rlm@228
|
259 (same-side? vert-1 vert-2 vert-3 p)
|
rlm@228
|
260 (same-side? vert-2 vert-3 vert-1 p)
|
rlm@228
|
261 (same-side? vert-3 vert-1 vert-2 p))))
|
rlm@228
|
262
|
rlm@228
|
263 (defn convex-bounds
|
rlm@228
|
264 "Returns the smallest square containing the given vertices, as a
|
rlm@228
|
265 vector of integers [left top width height]."
|
rlm@228
|
266 [uv-verts]
|
rlm@228
|
267 (let [xs (map first uv-verts)
|
rlm@228
|
268 ys (map second uv-verts)
|
rlm@228
|
269 x0 (Math/floor (apply min xs))
|
rlm@228
|
270 y0 (Math/floor (apply min ys))
|
rlm@228
|
271 x1 (Math/ceil (apply max xs))
|
rlm@228
|
272 y1 (Math/ceil (apply max ys))]
|
rlm@228
|
273 [x0 y0 (- x1 x0) (- y1 y0)]))
|
rlm@228
|
274 #+end_src
|
rlm@228
|
275
|
rlm@228
|
276 * Skin Creation
|
rlm@228
|
277
|
rlm@228
|
278 #+begin_src clojure
|
rlm@178
|
279 (defn touch-fn
|
rlm@178
|
280 "Returns a function which returns tactile sensory data when called
|
rlm@178
|
281 inside a running simulation."
|
rlm@178
|
282 [#^Geometry geo]
|
rlm@156
|
283 (let [feeler-coords (feeler-coordinates geo)
|
rlm@156
|
284 tris (triangles geo)
|
rlm@156
|
285 limit 0.1
|
rlm@156
|
286 ;;results (CollisionResults.)
|
rlm@156
|
287 ]
|
rlm@156
|
288 (if (empty? (touch-topology geo))
|
rlm@156
|
289 nil
|
rlm@156
|
290 (fn [node]
|
rlm@156
|
291 (let [sensor-origins
|
rlm@156
|
292 (map
|
rlm@156
|
293 #(map (partial local-to-world geo) %)
|
rlm@156
|
294 feeler-coords)
|
rlm@156
|
295 triangle-normals
|
rlm@156
|
296 (map (partial get-ray-direction geo)
|
rlm@156
|
297 tris)
|
rlm@156
|
298 rays
|
rlm@156
|
299 (flatten
|
rlm@156
|
300 (map (fn [origins norm]
|
rlm@156
|
301 (map #(doto (Ray. % norm)
|
rlm@156
|
302 (.setLimit limit)) origins))
|
rlm@156
|
303 sensor-origins triangle-normals))]
|
rlm@156
|
304 (vector
|
rlm@156
|
305 (touch-topology geo)
|
rlm@156
|
306 (vec
|
rlm@156
|
307 (for [ray rays]
|
rlm@156
|
308 (do
|
rlm@156
|
309 (let [results (CollisionResults.)]
|
rlm@156
|
310 (.collideWith node ray results)
|
rlm@156
|
311 (let [touch-objects
|
rlm@156
|
312 (filter #(not (= geo (.getGeometry %)))
|
rlm@156
|
313 results)]
|
rlm@156
|
314 (- 255
|
rlm@156
|
315 (if (empty? touch-objects) 255
|
rlm@156
|
316 (rem
|
rlm@156
|
317 (int
|
rlm@156
|
318 (* 255 (/ (.getDistance
|
rlm@156
|
319 (first touch-objects)) limit)))
|
rlm@156
|
320 256))))))))))))))
|
rlm@156
|
321
|
rlm@178
|
322 (defn touch!
|
rlm@178
|
323 "Endow the creature with the sense of touch. Returns a sequence of
|
rlm@178
|
324 functions, one for each body part with a tactile-sensor-proile,
|
rlm@178
|
325 each of which when called returns sensory data for that body part."
|
rlm@178
|
326 [#^Node creature]
|
rlm@178
|
327 (filter
|
rlm@178
|
328 (comp not nil?)
|
rlm@178
|
329 (map touch-fn
|
rlm@178
|
330 (filter #(isa? (class %) Geometry)
|
rlm@178
|
331 (node-seq creature)))))
|
rlm@228
|
332 #+end_src
|
rlm@156
|
333
|
rlm@228
|
334 * Visualizing Touch
|
rlm@228
|
335
|
rlm@228
|
336 #+begin_src clojure
|
rlm@188
|
337 (defn view-touch
|
rlm@189
|
338 "Creates a function which accepts a list of touch sensor-data and
|
rlm@189
|
339 displays each element to the screen."
|
rlm@188
|
340 []
|
rlm@187
|
341 (view-sense
|
rlm@187
|
342 (fn
|
rlm@187
|
343 [[coords sensor-data]]
|
rlm@187
|
344 (let [image (points->image coords)]
|
rlm@187
|
345 (dorun
|
rlm@187
|
346 (for [i (range (count coords))]
|
rlm@187
|
347 (.setRGB image ((coords i) 0) ((coords i) 1)
|
rlm@187
|
348 (gray (sensor-data i)))))
|
rlm@188
|
349 image))))
|
rlm@37
|
350 #+end_src
|
rlm@37
|
351
|
rlm@226
|
352 * Headers
|
rlm@226
|
353 #+begin_src clojure
|
rlm@226
|
354 (ns cortex.touch
|
rlm@226
|
355 "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry
|
rlm@226
|
356 to be outfitted with touch sensors with density determined by a UV
|
rlm@226
|
357 image. In this way a Geometry can know what parts of itself are
|
rlm@226
|
358 touching nearby objects. Reads specially prepared blender files to
|
rlm@226
|
359 construct this sense automatically."
|
rlm@226
|
360 {:author "Robert McIntyre"}
|
rlm@226
|
361 (:use (cortex world util sense))
|
rlm@226
|
362 (:use clojure.contrib.def)
|
rlm@226
|
363 (:import (com.jme3.scene Geometry Node Mesh))
|
rlm@226
|
364 (:import com.jme3.collision.CollisionResults)
|
rlm@226
|
365 (:import com.jme3.scene.VertexBuffer$Type)
|
rlm@226
|
366 (:import (com.jme3.math Triangle Vector3f Vector2f Ray Matrix4f)))
|
rlm@226
|
367 #+end_src
|
rlm@37
|
368
|
rlm@228
|
369 ;; Every Mesh has many triangles, each with its own index.
|
rlm@228
|
370 ;; Every vertex has its own index as well.
|
rlm@228
|
371
|
rlm@228
|
372 * Source Listing
|
rlm@228
|
373 * Next
|
rlm@228
|
374
|
rlm@228
|
375
|
rlm@226
|
376 * COMMENT Code Generation
|
rlm@39
|
377 #+begin_src clojure :tangle ../src/cortex/touch.clj
|
rlm@0
|
378 <<skin-main>>
|
rlm@0
|
379 #+end_src
|
rlm@0
|
380
|
rlm@68
|
381 #+begin_src clojure :tangle ../src/cortex/test/touch.clj
|
rlm@39
|
382 #+end_src
|
rlm@39
|
383
|
rlm@0
|
384
|
rlm@0
|
385
|
rlm@0
|
386
|
rlm@32
|
387
|
rlm@32
|
388
|
rlm@226
|
389
|