view org/touch.org @ 178:6fba17a74a57

refactored touch
author Robert McIntyre <rlm@mit.edu>
date Sat, 04 Feb 2012 07:05:54 -0700
parents 5af4ebe72b97
children 11bd5f0625ad
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
9 * Touch
11 My creatures need to be able to feel their environments. The idea here
12 is to create thousands of small /touch receptors/ along the geometries
13 which make up the creature's body. The number of touch receptors in a
14 given area is determined by how complicated that area is, as
15 determined by the total number of triangles in that region. This way,
16 complicated regions like the hands/face, etc. get more touch receptors
17 than simpler areas of the body.
19 #+name: skin-main
20 #+begin_src clojure
21 (ns cortex.touch
22 "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry
23 to be outfitted with touch sensors with density proportional to the
24 density of triangles along the surface of the Geometry. Enables a
25 Geometry to know what parts of itself are touching nearby objects."
26 {:author "Robert McIntyre"}
27 (:use (cortex world util sense))
28 (:use clojure.contrib.def)
29 (:import com.jme3.scene.Geometry)
30 (:import com.jme3.collision.CollisionResults)
31 (:import jme3tools.converters.ImageToAwt)
32 (:import (com.jme3.math Triangle Vector3f Ray)))
34 (cortex.import/mega-import-jme3)
36 (defn triangles
37 "Return a sequence of all the Triangles which compose a given
38 Geometry."
39 [#^Geometry geom]
40 (let
41 [mesh (.getMesh geom)
42 triangles (transient [])]
43 (dorun
44 (for [n (range (.getTriangleCount mesh))]
45 (let [tri (Triangle.)]
46 (.getTriangle mesh n tri)
47 ;; (.calculateNormal tri)
48 ;; (.calculateCenter tri)
49 (conj! triangles tri))))
50 (persistent! triangles)))
52 (defn get-ray-origin
53 "Return the origin which a Ray would have to have to be in the exact
54 center of a particular Triangle in the Geometry in World
55 Coordinates."
56 [geom tri]
57 (let [new (Vector3f.)]
58 (.calculateCenter tri)
59 (.localToWorld geom (.getCenter tri) new) new))
61 (defn get-ray-direction
62 "Return the direction which a Ray would have to have to be to point
63 normal to the Triangle, in coordinates relative to the center of the
64 Triangle."
65 [geom tri]
66 (let [n+c (Vector3f.)]
67 (.calculateNormal tri)
68 (.calculateCenter tri)
69 (.localToWorld
70 geom
71 (.add (.getCenter tri) (.getNormal tri)) n+c)
72 (.subtract n+c (get-ray-origin geom tri))))
74 ;; Every Mesh has many triangles, each with its own index.
75 ;; Every vertex has its own index as well.
77 (defn tactile-sensor-profile
78 "Return the touch-sensor distribution image in BufferedImage format,
79 or nil if it does not exist."
80 [#^Geometry obj]
81 (if-let [image-path (meta-data obj "touch")]
82 (load-image image-path)))
84 (defn triangle
85 "Get the triangle specified by triangle-index from the mesh within
86 bounds."
87 [#^Mesh mesh triangle-index]
88 (let [scratch (Triangle.)]
89 (.getTriangle mesh triangle-index scratch)
90 scratch))
92 (defn triangle-vertex-indices
93 "Get the triangle vertex indices of a given triangle from a given
94 mesh."
95 [#^Mesh mesh triangle-index]
96 (let [indices (int-array 3)]
97 (.getTriangle mesh triangle-index indices)
98 (vec indices)))
100 (defn vertex-UV-coord
101 "Get the uv-coordinates of the vertex named by vertex-index"
102 [#^Mesh mesh vertex-index]
103 (let [UV-buffer
104 (.getData
105 (.getBuffer
106 mesh
107 VertexBuffer$Type/TexCoord))]
108 [(.get UV-buffer (* vertex-index 2))
109 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
111 (defn triangle-UV-coord
112 "Get the uv-cooridnates of the triangle's verticies."
113 [#^Mesh mesh width height triangle-index]
114 (map (fn [[u v]] (vector (* width u) (* height v)))
115 (map (partial vertex-UV-coord mesh)
116 (triangle-vertex-indices mesh triangle-index))))
118 (defn same-side?
119 "Given the points p1 and p2 and the reference point ref, is point p
120 on the same side of the line that goes through p1 and p2 as ref is?"
121 [p1 p2 ref p]
122 (<=
123 0
124 (.dot
125 (.cross (.subtract p2 p1) (.subtract p p1))
126 (.cross (.subtract p2 p1) (.subtract ref p1)))))
128 (defn triangle-seq [#^Triangle tri]
129 [(.get1 tri) (.get2 tri) (.get3 tri)])
131 (defn vector3f-seq [#^Vector3f v]
132 [(.getX v) (.getY v) (.getZ v)])
134 (defn inside-triangle?
135 "Is the point inside the triangle?"
136 {:author "Dylan Holmes"}
137 [#^Triangle tri #^Vector3f p]
138 (let [[vert-1 vert-2 vert-3] (triangle-seq tri)]
139 (and
140 (same-side? vert-1 vert-2 vert-3 p)
141 (same-side? vert-2 vert-3 vert-1 p)
142 (same-side? vert-3 vert-1 vert-2 p))))
144 (defn triangle->matrix4f
145 "Converts the triangle into a 4x4 matrix: The first three columns
146 contain the vertices of the triangle; the last contains the unit
147 normal of the triangle. The bottom row is filled with 1s."
148 [#^Triangle t]
149 (let [mat (Matrix4f.)
150 [vert-1 vert-2 vert-3]
151 ((comp vec map) #(.get t %) (range 3))
152 unit-normal (do (.calculateNormal t)(.getNormal t))
153 vertices [vert-1 vert-2 vert-3 unit-normal]]
154 (dorun
155 (for [row (range 4) col (range 3)]
156 (do
157 (.set mat col row (.get (vertices row)col))
158 (.set mat 3 row 1))))
159 mat))
161 (defn triangle-transformation
162 "Returns the affine transformation that converts each vertex in the
163 first triangle into the corresponding vertex in the second
164 triangle."
165 [#^Triangle tri-1 #^Triangle tri-2]
166 (.mult
167 (triangle->matrix4f tri-2)
168 (.invert (triangle->matrix4f tri-1))))
170 (defn point->vector2f [[u v]]
171 (Vector2f. u v))
173 (defn vector2f->vector3f [v]
174 (Vector3f. (.getX v) (.getY v) 0))
176 (defn map-triangle [f #^Triangle tri]
177 (Triangle.
178 (f 0 (.get1 tri))
179 (f 1 (.get2 tri))
180 (f 2 (.get3 tri))))
182 (defn points->triangle
183 "Convert a list of points into a triangle."
184 [points]
185 (apply #(Triangle. %1 %2 %3)
186 (map (fn [point]
187 (let [point (vec point)]
188 (Vector3f. (get point 0 0)
189 (get point 1 0)
190 (get point 2 0))))
191 (take 3 points))))
193 (defn convex-bounds
194 "Returns the smallest square containing the given vertices, as a
195 vector of integers [left top width height]."
196 [uv-verts]
197 (let [xs (map first uv-verts)
198 ys (map second uv-verts)
199 x0 (Math/floor (apply min xs))
200 y0 (Math/floor (apply min ys))
201 x1 (Math/ceil (apply max xs))
202 y1 (Math/ceil (apply max ys))]
203 [x0 y0 (- x1 x0) (- y1 y0)]))
205 (defn sensors-in-triangle
206 "Locate the touch sensors in the triangle, returning a map of their
207 UV and geometry-relative coordinates."
208 [image mesh tri-index]
209 (let [width (.getWidth image)
210 height (.getHeight image)
211 UV-vertex-coords (triangle-UV-coord mesh width height tri-index)
212 bounds (convex-bounds UV-vertex-coords)
214 cutout-triangle (points->triangle UV-vertex-coords)
215 UV-sensor-coords
216 (filter (comp (partial inside-triangle? cutout-triangle)
217 (fn [[u v]] (Vector3f. u v 0)))
218 (white-coordinates image bounds))
219 UV->geometry (triangle-transformation
220 cutout-triangle
221 (triangle mesh tri-index))
222 geometry-sensor-coords
223 (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0)))
224 UV-sensor-coords)]
225 {:UV UV-sensor-coords :geometry geometry-sensor-coords}))
227 (defn-memo locate-feelers
228 "Search the geometry's tactile UV profile for touch sensors,
229 returning their positions in geometry-relative coordinates."
230 [#^Geometry geo]
231 (let [mesh (.getMesh geo)
232 num-triangles (.getTriangleCount mesh)]
233 (if-let [image (tactile-sensor-profile geo)]
234 (map
235 (partial sensors-in-triangle image mesh)
236 (range num-triangles))
237 (repeat (.getTriangleCount mesh) {:UV nil :geometry nil}))))
239 (defn-memo touch-topology
240 "Return a sequence of vectors of the form [x y] describing the
241 \"topology\" of the tactile sensors. Points that are close together
242 in the touch-topology are generally close together in the simulation."
243 [#^Gemoetry geo]
244 (vec (collapse (reduce concat (map :UV (locate-feelers geo))))))
246 (defn-memo feeler-coordinates
247 "The location of the touch sensors in world-space coordinates."
248 [#^Geometry geo]
249 (vec (map :geometry (locate-feelers geo))))
251 (defn touch-fn
252 "Returns a function which returns tactile sensory data when called
253 inside a running simulation."
254 [#^Geometry geo]
255 (let [feeler-coords (feeler-coordinates geo)
256 tris (triangles geo)
257 limit 0.1
258 ;;results (CollisionResults.)
259 ]
260 (if (empty? (touch-topology geo))
261 nil
262 (fn [node]
263 (let [sensor-origins
264 (map
265 #(map (partial local-to-world geo) %)
266 feeler-coords)
267 triangle-normals
268 (map (partial get-ray-direction geo)
269 tris)
270 rays
271 (flatten
272 (map (fn [origins norm]
273 (map #(doto (Ray. % norm)
274 (.setLimit limit)) origins))
275 sensor-origins triangle-normals))]
276 (vector
277 (touch-topology geo)
278 (vec
279 (for [ray rays]
280 (do
281 (let [results (CollisionResults.)]
282 (.collideWith node ray results)
283 (let [touch-objects
284 (filter #(not (= geo (.getGeometry %)))
285 results)]
286 (- 255
287 (if (empty? touch-objects) 255
288 (rem
289 (int
290 (* 255 (/ (.getDistance
291 (first touch-objects)) limit)))
292 256))))))))))))))
294 (defn touch!
295 "Endow the creature with the sense of touch. Returns a sequence of
296 functions, one for each body part with a tactile-sensor-proile,
297 each of which when called returns sensory data for that body part."
298 [#^Node creature]
299 (filter
300 (comp not nil?)
301 (map touch-fn
302 (filter #(isa? (class %) Geometry)
303 (node-seq creature)))))
306 #+end_src
309 * Example
311 #+name: touch-test
312 #+begin_src clojure
313 (ns cortex.test.touch
314 (:use (cortex world util touch))
315 (:import
316 com.jme3.scene.shape.Sphere
317 com.jme3.math.ColorRGBA
318 com.jme3.math.Vector3f
319 com.jme3.material.RenderState$BlendMode
320 com.jme3.renderer.queue.RenderQueue$Bucket
321 com.jme3.scene.shape.Box
322 com.jme3.scene.Node))
324 (defn ray-origin-debug
325 [ray color]
326 (make-shape
327 (assoc base-shape
328 :shape (Sphere. 5 5 0.05)
329 :name "arrow"
330 :color color
331 :texture false
332 :physical? false
333 :position
334 (.getOrigin ray))))
336 (defn ray-debug [ray color]
337 (make-shape
338 (assoc
339 base-shape
340 :name "debug-ray"
341 :physical? false
342 :shape (com.jme3.scene.shape.Line.
343 (.getOrigin ray)
344 (.add
345 (.getOrigin ray)
346 (.mult (.getDirection ray)
347 (float (.getLimit ray))))))))
350 (defn contact-color [contacts]
351 (case contacts
352 0 ColorRGBA/Gray
353 1 ColorRGBA/Red
354 2 ColorRGBA/Green
355 3 ColorRGBA/Yellow
356 4 ColorRGBA/Orange
357 5 ColorRGBA/Red
358 6 ColorRGBA/Magenta
359 7 ColorRGBA/Pink
360 8 ColorRGBA/White))
362 (defn update-ray-debug [node ray contacts]
363 (let [origin (.getChild node 0)]
364 (.setLocalTranslation origin (.getOrigin ray))
365 (.setColor (.getMaterial origin) "Color" (contact-color contacts))))
367 (defn init-node
368 [debug-node rays]
369 (.detachAllChildren debug-node)
370 (dorun
371 (for [ray rays]
372 (do
373 (.attachChild
374 debug-node
375 (doto (Node.)
376 (.attachChild (ray-origin-debug ray ColorRGBA/Gray))
377 (.attachChild (ray-debug ray ColorRGBA/Gray))
378 ))))))
380 (defn manage-ray-debug-node [debug-node geom touch-data limit]
381 (let [rays (normal-rays limit geom)]
382 (if (not= (count (.getChildren debug-node)) (count touch-data))
383 (init-node debug-node rays))
384 (dorun
385 (for [n (range (count touch-data))]
386 (update-ray-debug
387 (.getChild debug-node n) (nth rays n) (nth touch-data n))))))
389 (defn transparent-sphere []
390 (doto
391 (make-shape
392 (merge base-shape
393 {:position (Vector3f. 0 2 0)
394 :name "the blob."
395 :material "Common/MatDefs/Misc/Unshaded.j3md"
396 :texture "Textures/purpleWisp.png"
397 :physical? true
398 :mass 70
399 :color ColorRGBA/Blue
400 :shape (Sphere. 10 10 1)}))
401 (-> (.getMaterial)
402 (.getAdditionalRenderState)
403 (.setBlendMode RenderState$BlendMode/Alpha))
404 (.setQueueBucket RenderQueue$Bucket/Transparent)))
406 (defn transparent-box []
407 (doto
408 (make-shape
409 (merge base-shape
410 {:position (Vector3f. 0 2 0)
411 :name "box"
412 :material "Common/MatDefs/Misc/Unshaded.j3md"
413 :texture "Textures/purpleWisp.png"
414 :physical? true
415 :mass 70
416 :color ColorRGBA/Blue
417 :shape (Box. 1 1 1)}))
418 (-> (.getMaterial)
419 (.getAdditionalRenderState)
420 (.setBlendMode RenderState$BlendMode/Alpha))
421 (.setQueueBucket RenderQueue$Bucket/Transparent)))
423 (defn transparent-floor []
424 (doto
425 (box 5 0.2 5 :mass 0 :position (Vector3f. 0 -2 0)
426 :material "Common/MatDefs/Misc/Unshaded.j3md"
427 :texture "Textures/redWisp.png"
428 :name "floor")
429 (-> (.getMaterial)
430 (.getAdditionalRenderState)
431 (.setBlendMode RenderState$BlendMode/Alpha))
432 (.setQueueBucket RenderQueue$Bucket/Transparent)))
434 (defn test-skin
435 "Testing touch:
436 you should see a ball which responds to the table
437 and whatever balls hit it."
438 []
439 (let [b
440 ;;(transparent-box)
441 (transparent-sphere)
442 ;;(sphere)
443 f (transparent-floor)
444 debug-node (Node.)
445 node (doto (Node.) (.attachChild b) (.attachChild f))
446 root-node (doto (Node.) (.attachChild node)
447 (.attachChild debug-node))
448 ]
450 (world
451 root-node
452 {"key-return" (fire-cannon-ball node)}
453 (fn [world]
454 ;; (Capture/SimpleCaptureVideo
455 ;; world
456 ;; (file-str "/home/r/proj/cortex/tmp/blob.avi"))
457 ;; (no-logging)
458 ;;(enable-debug world)
459 ;; (set-accuracy world (/ 1 60))
460 )
462 (fn [& _]
463 (let [sensitivity 0.2
464 touch-data (touch-percieve sensitivity b node)]
465 (manage-ray-debug-node debug-node b touch-data sensitivity))
466 ))))
469 #+end_src
475 * COMMENT code generation
476 #+begin_src clojure :tangle ../src/cortex/touch.clj
477 <<skin-main>>
478 #+end_src
480 #+begin_src clojure :tangle ../src/cortex/test/touch.clj
481 <<touch-test>>
482 #+end_src