view org/touch.org @ 238:3fa49ff1649a

going to recode touch.org to make it look better
author Robert McIntyre <rlm@mit.edu>
date Sun, 12 Feb 2012 12:10:51 -0700
parents 712bd7e5b148
children 78a640e3bc55
line wrap: on
line source
1 #+title: Simulated Sense of Touch
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description: Simulated touch for AI research using JMonkeyEngine and clojure.
5 #+keywords: simulation, tactile sense, jMonkeyEngine3, clojure
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
11 * Touch
13 Touch is critical to navigation and spatial reasoning and as such I
14 need a simulated version of it to give to my AI creatures.
16 However, touch in my virtual can not exactly correspond to human touch
17 because my creatures are made out of completely rigid segments that
18 don't deform like human skin.
20 Human skin has a wide array of touch sensors, each of which speciliaze
21 in detecting different vibrational modes and pressures. These sensors
22 can integrate a vast expanse of skin (i.e. your entire palm), or a
23 tiny patch of skin at the tip of your finger. The hairs of the skin
24 help detect objects before they even come into contact with the skin
25 proper.
27 Instead of measuring deformation or vibration, I surround each rigid
28 part with a plenitude of hair-like objects which do not interact with
29 the physical world. Physical objects can pass through them with no
30 effect. The hairs are able to measure contact with other objects, and
31 constantly report how much of their extent is covered. So, even though
32 the creature's body parts do not deform, the hairs create a margin
33 around those body parts which achieves a sense of touch which is a
34 hybrid between a human's sense of deformation and sense from hairs.
36 Implementing touch in jMonkeyEngine follows a different techinal route
37 than vision and hearing. Those two senses piggybacked off
38 jMonkeyEngine's 3D audio and video rendering subsystems. To simulate
39 Touch, I use jMonkeyEngine's physics system to execute many small
40 collision detections, one for each "hair". The placement of the
41 "hairs" is determined by a UV-mapped image which shows where each hair
42 should be on the 3D surface of the body.
45 * Defining Touch Meta-Data in Blender
47 Each geometry can have a single UV map which describes the position
48 and length of the "hairs" which will constitute its sense of
49 touch. This image path is stored under the "touch" key. The image
50 itself is grayscale, with black meaning a hair length of 0 (no hair is
51 present) and white meaning a hair length of =scale=, which is a float
52 stored under the key "scale". If the pixel is gray then the resultant
53 hair length is linearly interpolated between 0 and =scale=. I call
54 these "hairs" /feelers/.
56 #+name: meta-data
57 #+begin_src clojure
58 (defn tactile-sensor-profile
59 "Return the touch-sensor distribution image in BufferedImage format,
60 or nil if it does not exist."
61 [#^Geometry obj]
62 (if-let [image-path (meta-data obj "touch")]
63 (load-image image-path)))
65 (defn tactile-scale
66 "Return the maximum length of a hair. All hairs are scalled between
67 0.0 and this length, depending on their color. Black is 0, and
68 white is maximum length, and everything in between is scalled
69 linearlly. Default scale is 0.01 jMonkeyEngine units."
70 [#^Geometry obj]
71 (if-let [scale (meta-data obj "scale")]
72 scale 0.1))
73 #+end_src
75 ** TODO add image showing example touch-uv map
76 ** TODO add metadata display for worm
79 * Skin Creation
80 * TODO get the actual lengths for each hair
82 #+begin_src clojure
83 pixel-triangles
84 xyz-triangles
85 conversions (map triangles->affine-transform pixel-triangles
86 xyz-triangles)
88 #+end_src
91 =(touch-kernel)= generates the functions which implement the sense of
92 touch for a creature. These functions must do 6 things to obtain touch
93 data.
95 - Get the tactile profile image and scale paramaters which describe
96 the layout of feelers along the object's surface.
97 - Get the lengths of each feeler by analyzing the color of the
98 pixels in the tactile profile image.
99 - Find the triangles which make up the mesh in pixel-space and in
100 world-space.
101 - Find the coordinates of each pixel in world-space. These
102 coordinates are the origins of the feelers.
103 - Calculate the normals of the triangles in world space, and add
104 them to each of the origins of the feelers. These are the
105 normalized coordinates of the tips of the feelers.
106 - Generate some sort of topology for the sensors.
108 #+name: kernel
109 #+begin_src clojure
110 (in-ns 'cortex.touch)
112 (declare touch-topology touch-hairs set-ray)
114 (defn touch-kernel
115 "Constructs a function which will return tactile sensory data from
116 'geo when called from inside a running simulation"
117 [#^Geometry geo]
118 (let [[ray-reference-origins
119 ray-reference-tips
120 ray-lengths] (touch-hairs geo)
121 current-rays (map (fn [] (Ray.)) ray-reference-origins)
122 topology (touch-topology geo)]
123 (if (empty? ray-reference-origins) nil
124 (fn [node]
125 (let [transform (.getWorldMatrix geo)]
126 (dorun
127 (map (fn [ray ref-origin ref-tip length]
128 (set-ray ray transform ref-origin ref-tip length))
129 current-rays ray-reference-origins
130 ray-reference-tips ray-lengths))
131 (vector
132 topology
133 (vec
134 (for [ray current-rays]
135 (do
136 (let [results (CollisionResults.)]
137 (.collideWith node ray results)
138 (let [touch-objects
139 (filter #(not (= geo (.getGeometry %)))
140 results)]
141 [(if (empty? touch-objects)
142 (.getLimit ray)
143 (.getDistance (first touch-objects)))
144 (.getLimit ray)])))))))))))
146 (defn touch-kernel*
147 "Returns a function which returns tactile sensory data when called
148 inside a running simulation."
149 [#^Geometry geo]
150 (let [feeler-coords (feeler-coordinates geo)
151 tris (triangles geo)
152 limit (tactile-scale geo)]
153 (if (empty? (touch-topology geo))
154 nil
155 (fn [node]
156 (let [sensor-origins
157 (map
158 #(map (partial local-to-world geo) %)
159 feeler-coords)
160 triangle-normals
161 (map (partial get-ray-direction geo)
162 tris)
163 rays
164 (flatten
165 (map (fn [origins norm]
166 (map #(doto (Ray. % norm)
167 (.setLimit limit)) origins))
168 sensor-origins triangle-normals))]
169 (vector
170 (touch-topology geo)
171 (vec
172 (for [ray rays]
173 (do
174 (let [results (CollisionResults.)]
175 (.collideWith node ray results)
176 (let [touch-objects
177 (filter #(not (= geo (.getGeometry %)))
178 results)]
179 [(if (empty? touch-objects)
180 limit (.getDistance (first touch-objects)))
181 limit])))))))))))
183 (defn touch!
184 "Endow the creature with the sense of touch. Returns a sequence of
185 functions, one for each body part with a tactile-sensor-proile,
186 each of which when called returns sensory data for that body part."
187 [#^Node creature]
188 (filter
189 (comp not nil?)
190 (map touch-kernel
191 (filter #(isa? (class %) Geometry)
192 (node-seq creature)))))
193 #+end_src
195 * Sensor Related Functions
197 These functions analyze the touch-sensor-profile image convert the
198 location of each touch sensor from pixel coordinates to UV-coordinates
199 and XYZ-coordinates.
201 #+name: sensors
202 #+begin_src clojure
203 (defn sensors-in-triangle
204 "Locate the touch sensors in the triangle, returning a map of their
205 UV and geometry-relative coordinates."
206 [image mesh tri-index]
207 (let [width (.getWidth image)
208 height (.getHeight image)
209 UV-vertex-coords (triangle-UV-coord mesh width height tri-index)
210 bounds (convex-bounds UV-vertex-coords)
212 cutout-triangle (points->triangle UV-vertex-coords)
213 UV-sensor-coords
214 (filter (comp (partial inside-triangle? cutout-triangle)
215 (fn [[u v]] (Vector3f. u v 0)))
216 (white-coordinates image bounds))
217 UV->geometry (triangle-transformation
218 cutout-triangle
219 (mesh-triangle mesh tri-index))
220 geometry-sensor-coords
221 (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0)))
222 UV-sensor-coords)]
223 {:UV UV-sensor-coords :geometry geometry-sensor-coords}))
225 (defn-memo locate-feelers
226 "Search the geometry's tactile UV profile for touch sensors,
227 returning their positions in geometry-relative coordinates."
228 [#^Geometry geo]
229 (let [mesh (.getMesh geo)
230 num-triangles (.getTriangleCount mesh)]
231 (if-let [image (tactile-sensor-profile geo)]
232 (map
233 (partial sensors-in-triangle image mesh)
234 (range num-triangles))
235 (repeat (.getTriangleCount mesh) {:UV nil :geometry nil}))))
237 (defn-memo touch-topology
238 "Return a sequence of vectors of the form [x y] describing the
239 \"topology\" of the tactile sensors. Points that are close together
240 in the touch-topology are generally close together in the simulation."
241 [#^Gemoetry geo]
242 (vec (collapse (reduce concat (map :UV (locate-feelers geo))))))
244 (defn-memo feeler-coordinates
245 "The location of the touch sensors in world-space coordinates."
246 [#^Geometry geo]
247 (vec (map :geometry (locate-feelers geo))))
248 #+end_src
253 * Visualizing Touch
254 #+name: visualization
255 #+begin_src clojure
256 (in-ns 'cortex.touch)
258 (defn touch->gray
259 "Convert a pair of [distance, max-distance] into a grayscale pixel"
260 [distance max-distance]
261 (gray
262 (- 255
263 (rem
264 (int
265 (* 255 (/ distance max-distance)))
266 256))))
268 (defn view-touch
269 "Creates a function which accepts a list of touch sensor-data and
270 displays each element to the screen."
271 []
272 (view-sense
273 (fn
274 [[coords sensor-data]]
275 (let [image (points->image coords)]
276 (dorun
277 (for [i (range (count coords))]
278 (.setRGB image ((coords i) 0) ((coords i) 1)
279 (apply touch->gray (sensor-data i)))))
280 image))))
281 #+end_src
285 * Triangle Manipulation Functions
287 The rigid bodies which make up a creature have an underlying
288 =Geometry=, which is a =Mesh= plus a =Material= and other important
289 data involved with displaying the body.
291 A =Mesh= is composed of =Triangles=, and each =Triangle= has three
292 verticies which have coordinates in XYZ space and UV space.
294 Here, =(triangles)= gets all the triangles which compose a mesh, and
295 =(triangle-UV-coord)= returns the the UV coordinates of the verticies
296 of a triangle.
298 #+name: triangles-1
299 #+begin_src clojure
300 (defn triangles
301 "Return a sequence of all the Triangles which compose a given
302 Geometry."
303 [#^Geometry geom]
304 (let
305 [mesh (.getMesh geom)
306 triangles (transient [])]
307 (dorun
308 (for [n (range (.getTriangleCount mesh))]
309 (let [tri (Triangle.)]
310 (.getTriangle mesh n tri)
311 ;; (.calculateNormal tri)
312 ;; (.calculateCenter tri)
313 (conj! triangles tri))))
314 (persistent! triangles)))
316 (defn mesh-triangle
317 "Get the triangle specified by triangle-index from the mesh within
318 bounds."
319 [#^Mesh mesh triangle-index]
320 (let [scratch (Triangle.)]
321 (.getTriangle mesh triangle-index scratch)
322 scratch))
324 (defn triangle-vertex-indices
325 "Get the triangle vertex indices of a given triangle from a given
326 mesh."
327 [#^Mesh mesh triangle-index]
328 (let [indices (int-array 3)]
329 (.getTriangle mesh triangle-index indices)
330 (vec indices)))
332 (defn vertex-UV-coord
333 "Get the UV-coordinates of the vertex named by vertex-index"
334 [#^Mesh mesh vertex-index]
335 (let [UV-buffer
336 (.getData
337 (.getBuffer
338 mesh
339 VertexBuffer$Type/TexCoord))]
340 [(.get UV-buffer (* vertex-index 2))
341 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
343 (defn triangle-UV-coord
344 "Get the UV-cooridnates of the triangle's verticies."
345 [#^Mesh mesh width height triangle-index]
346 (map (fn [[u v]] (vector (* width u) (* height v)))
347 (map (partial vertex-UV-coord mesh)
348 (triangle-vertex-indices mesh triangle-index))))
349 #+end_src
351 * Schrapnel Conversion Functions
353 It is convienent to treat a =Triangle= as a sequence of verticies, and
354 a =Vector2f= and =Vector3f= as a sequence of floats. These conversion
355 functions make this easy. If these classes implemented =Iterable= then
356 this code would not be necessary. Hopefully they will in the future.
358 #+name: triangles-2
359 #+begin_src clojure
360 (defn triangle-seq [#^Triangle tri]
361 [(.get1 tri) (.get2 tri) (.get3 tri)])
363 (defn vector3f-seq [#^Vector3f v]
364 [(.getX v) (.getY v) (.getZ v)])
366 (defn point->vector2f [[u v]]
367 (Vector2f. u v))
369 (defn vector2f->vector3f [v]
370 (Vector3f. (.getX v) (.getY v) 0))
372 (defn map-triangle [f #^Triangle tri]
373 (Triangle.
374 (f 0 (.get1 tri))
375 (f 1 (.get2 tri))
376 (f 2 (.get3 tri))))
378 (defn points->triangle
379 "Convert a list of points into a triangle."
380 [points]
381 (apply #(Triangle. %1 %2 %3)
382 (map (fn [point]
383 (let [point (vec point)]
384 (Vector3f. (get point 0 0)
385 (get point 1 0)
386 (get point 2 0))))
387 (take 3 points))))
388 #+end_src
390 * Triangle Affine Transforms
392 The position of each hair is stored in a 2D image in UV
393 coordinates. To place the hair in 3D space we must convert from UV
394 coordinates to XYZ coordinates. Each =Triangle= has coordinates in
395 both UV-space and XYZ-space, which defines a unique [[http://mathworld.wolfram.com/AffineTransformation.html ][Affine Transform]]
396 for translating any coordinate within the UV triangle to the
397 cooresponding coordinate in the XYZ triangle.
399 #+name: triangles-3
400 #+begin_src clojure
401 (defn triangle->matrix4f
402 "Converts the triangle into a 4x4 matrix: The first three columns
403 contain the vertices of the triangle; the last contains the unit
404 normal of the triangle. The bottom row is filled with 1s."
405 [#^Triangle t]
406 (let [mat (Matrix4f.)
407 [vert-1 vert-2 vert-3]
408 ((comp vec map) #(.get t %) (range 3))
409 unit-normal (do (.calculateNormal t)(.getNormal t))
410 vertices [vert-1 vert-2 vert-3 unit-normal]]
411 (dorun
412 (for [row (range 4) col (range 3)]
413 (do
414 (.set mat col row (.get (vertices row)col))
415 (.set mat 3 row 1))))
416 mat))
418 (defn triangle-transformation
419 "Returns the affine transformation that converts each vertex in the
420 first triangle into the corresponding vertex in the second
421 triangle."
422 [#^Triangle tri-1 #^Triangle tri-2]
423 (.mult
424 (triangle->matrix4f tri-2)
425 (.invert (triangle->matrix4f tri-1))))
426 #+end_src
428 * Triangle Boundaries
430 For efficiency's sake I will divide the UV-image into small squares
431 which inscribe each UV-triangle, then extract the points which lie
432 inside the triangle and map them to 3D-space using
433 =(triangle-transform)= above. To do this I need a function,
434 =(inside-triangle?)=, which determines whether a point is inside a
435 triangle in 2D UV-space.
437 #+name: triangles-4
438 #+begin_src clojure
439 (defn convex-bounds
440 "Returns the smallest square containing the given vertices, as a
441 vector of integers [left top width height]."
442 [uv-verts]
443 (let [xs (map first uv-verts)
444 ys (map second uv-verts)
445 x0 (Math/floor (apply min xs))
446 y0 (Math/floor (apply min ys))
447 x1 (Math/ceil (apply max xs))
448 y1 (Math/ceil (apply max ys))]
449 [x0 y0 (- x1 x0) (- y1 y0)]))
451 (defn same-side?
452 "Given the points p1 and p2 and the reference point ref, is point p
453 on the same side of the line that goes through p1 and p2 as ref is?"
454 [p1 p2 ref p]
455 (<=
456 0
457 (.dot
458 (.cross (.subtract p2 p1) (.subtract p p1))
459 (.cross (.subtract p2 p1) (.subtract ref p1)))))
461 (defn inside-triangle?
462 "Is the point inside the triangle?"
463 {:author "Dylan Holmes"}
464 [#^Triangle tri #^Vector3f p]
465 (let [[vert-1 vert-2 vert-3] (triangle-seq tri)]
466 (and
467 (same-side? vert-1 vert-2 vert-3 p)
468 (same-side? vert-2 vert-3 vert-1 p)
469 (same-side? vert-3 vert-1 vert-2 p))))
470 #+end_src
473 * Physics Collision Objects
475 The "hairs" are actually =Rays= which extend from a point on a
476 =Triangle= in the =Mesh= normal to the =Triangle's= surface.
478 #+name: rays
479 #+begin_src clojure
480 (defn get-ray-origin
481 "Return the origin which a Ray would have to have to be in the exact
482 center of a particular Triangle in the Geometry in World
483 Coordinates."
484 [geom tri]
485 (let [new (Vector3f.)]
486 (.calculateCenter tri)
487 (.localToWorld geom (.getCenter tri) new) new))
489 (defn get-ray-direction
490 "Return the direction which a Ray would have to have to be to point
491 normal to the Triangle, in coordinates relative to the center of the
492 Triangle."
493 [geom tri]
494 (let [n+c (Vector3f.)]
495 (.calculateNormal tri)
496 (.calculateCenter tri)
497 (.localToWorld
498 geom
499 (.add (.getCenter tri) (.getNormal tri)) n+c)
500 (.subtract n+c (get-ray-origin geom tri))))
501 #+end_src
502 * Headers
504 #+name: touch-header
505 #+begin_src clojure
506 (ns cortex.touch
507 "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry
508 to be outfitted with touch sensors with density determined by a UV
509 image. In this way a Geometry can know what parts of itself are
510 touching nearby objects. Reads specially prepared blender files to
511 construct this sense automatically."
512 {:author "Robert McIntyre"}
513 (:use (cortex world util sense))
514 (:use clojure.contrib.def)
515 (:import (com.jme3.scene Geometry Node Mesh))
516 (:import com.jme3.collision.CollisionResults)
517 (:import com.jme3.scene.VertexBuffer$Type)
518 (:import (com.jme3.math Triangle Vector3f Vector2f Ray Matrix4f)))
519 #+end_src
521 * Adding Touch to the Worm
523 #+name: test-touch
524 #+begin_src clojure
525 (ns cortex.test.touch
526 (:use (cortex world util sense body touch))
527 (:use cortex.test.body))
529 (cortex.import/mega-import-jme3)
531 (defn test-touch []
532 (let [the-worm (doto (worm) (body!))
533 touch (touch! the-worm)
534 touch-display (view-touch)]
535 (world (nodify [the-worm (floor)])
536 standard-debug-controls
538 (fn [world]
539 (light-up-everything world))
541 (fn [world tpf]
542 (touch-display (map #(% (.getRootNode world)) touch))))))
543 #+end_src
544 * Source Listing
545 * Next
548 * COMMENT Code Generation
549 #+begin_src clojure :tangle ../src/cortex/touch.clj
550 <<touch-header>>
551 <<meta-data>>
552 <<triangles-1>>
553 <<triangles-2>>
554 <<triangles-3>>
555 <<triangles-4>>
556 <<sensors>>
557 <<rays>>
558 <<kernel>>
559 <<visualization>>
560 #+end_src
563 #+begin_src clojure :tangle ../src/cortex/test/touch.clj
564 <<test-touch>>
565 #+end_src