Mercurial > cortex
comparison org/touch.org @ 177:5af4ebe72b97
renamed skin.org to touch.org
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sat, 04 Feb 2012 06:54:14 -0700 |
parents | org/skin.org@e8df6e76c3e5 |
children | 6fba17a74a57 |
comparison
equal
deleted
inserted
replaced
176:026f69582022 | 177:5af4ebe72b97 |
---|---|
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 | |
8 | |
9 | |
10 * Touch | |
11 | |
12 My creatures need to be able to feel their environments. The idea here | |
13 is to create thousands of small /touch receptors/ along the geometries | |
14 which make up the creature's body. The number of touch receptors in a | |
15 given area is determined by how complicated that area is, as | |
16 determined by the total number of triangles in that region. This way, | |
17 complicated regions like the hands/face, etc. get more touch receptors | |
18 than simpler areas of the body. | |
19 | |
20 #+name: skin-main | |
21 #+begin_src clojure | |
22 (ns cortex.touch | |
23 "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry | |
24 to be outfitted with touch sensors with density proportional to the | |
25 density of triangles along the surface of the Geometry. Enables a | |
26 Geometry to know what parts of itself are touching nearby objects." | |
27 {:author "Robert McIntyre"} | |
28 (:use (cortex world util sense)) | |
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))) | |
33 | |
34 (use 'clojure.contrib.def) | |
35 (cortex.import/mega-import-jme3) | |
36 | |
37 (defn triangles | |
38 "Return a sequence of all the Triangles which compose a given | |
39 Geometry." | |
40 [#^Geometry geom] | |
41 (let | |
42 [mesh (.getMesh geom) | |
43 triangles (transient [])] | |
44 (dorun | |
45 (for [n (range (.getTriangleCount mesh))] | |
46 (let [tri (Triangle.)] | |
47 (.getTriangle mesh n tri) | |
48 ;; (.calculateNormal tri) | |
49 ;; (.calculateCenter tri) | |
50 (conj! triangles tri)))) | |
51 (persistent! triangles))) | |
52 | |
53 (defn get-ray-origin | |
54 "Return the origin which a Ray would have to have to be in the exact | |
55 center of a particular Triangle in the Geometry in World | |
56 Coordinates." | |
57 [geom tri] | |
58 (let [new (Vector3f.)] | |
59 (.calculateCenter tri) | |
60 (.localToWorld geom (.getCenter tri) new) new)) | |
61 | |
62 (defn get-ray-direction | |
63 "Return the direction which a Ray would have to have to be to point | |
64 normal to the Triangle, in coordinates relative to the center of the | |
65 Triangle." | |
66 [geom tri] | |
67 (let [n+c (Vector3f.)] | |
68 (.calculateNormal tri) | |
69 (.calculateCenter tri) | |
70 (.localToWorld | |
71 geom | |
72 (.add (.getCenter tri) (.getNormal tri)) n+c) | |
73 (.subtract n+c (get-ray-origin geom tri)))) | |
74 | |
75 ;; Every Mesh has many triangles, each with its own index. | |
76 ;; Every vertex has its own index as well. | |
77 | |
78 (defn tactile-sensor-image | |
79 "Return the touch-sensor distribution image in BufferedImage format, | |
80 or nil if it does not exist." | |
81 [#^Geometry obj] | |
82 (if-let [image-path (meta-data obj "touch")] | |
83 (ImageToAwt/convert | |
84 (.getImage | |
85 (.loadTexture | |
86 (asset-manager) | |
87 image-path)) | |
88 false false 0))) | |
89 | |
90 | |
91 | |
92 (defn triangle | |
93 "Get the triangle specified by triangle-index from the mesh within | |
94 bounds." | |
95 [#^Mesh mesh triangle-index] | |
96 (let [scratch (Triangle.)] | |
97 (.getTriangle mesh triangle-index scratch) | |
98 scratch)) | |
99 | |
100 (defn triangle-vertex-indices | |
101 "Get the triangle vertex indices of a given triangle from a given | |
102 mesh." | |
103 [#^Mesh mesh triangle-index] | |
104 (let [indices (int-array 3)] | |
105 (.getTriangle mesh triangle-index indices) | |
106 (vec indices))) | |
107 | |
108 (defn vertex-UV-coord | |
109 "Get the uv-coordinates of the vertex named by vertex-index" | |
110 [#^Mesh mesh vertex-index] | |
111 (let [UV-buffer | |
112 (.getData | |
113 (.getBuffer | |
114 mesh | |
115 VertexBuffer$Type/TexCoord))] | |
116 [(.get UV-buffer (* vertex-index 2)) | |
117 (.get UV-buffer (+ 1 (* vertex-index 2)))])) | |
118 | |
119 (defn triangle-UV-coord | |
120 "Get the uv-cooridnates of the triangle's verticies." | |
121 [#^Mesh mesh width height triangle-index] | |
122 (map (fn [[u v]] (vector (* width u) (* height v))) | |
123 (map (partial vertex-UV-coord mesh) | |
124 (triangle-vertex-indices mesh triangle-index)))) | |
125 | |
126 (defn same-side? | |
127 "Given the points p1 and p2 and the reference point ref, is point p | |
128 on the same side of the line that goes through p1 and p2 as ref is?" | |
129 [p1 p2 ref p] | |
130 (<= | |
131 0 | |
132 (.dot | |
133 (.cross (.subtract p2 p1) (.subtract p p1)) | |
134 (.cross (.subtract p2 p1) (.subtract ref p1))))) | |
135 | |
136 (defn triangle-seq [#^Triangle tri] | |
137 [(.get1 tri) (.get2 tri) (.get3 tri)]) | |
138 | |
139 (defn vector3f-seq [#^Vector3f v] | |
140 [(.getX v) (.getY v) (.getZ v)]) | |
141 | |
142 (defn inside-triangle? | |
143 "Is the point inside the triangle?" | |
144 {:author "Dylan Holmes"} | |
145 [#^Triangle tri #^Vector3f p] | |
146 (let [[vert-1 vert-2 vert-3] (triangle-seq tri)] | |
147 (and | |
148 (same-side? vert-1 vert-2 vert-3 p) | |
149 (same-side? vert-2 vert-3 vert-1 p) | |
150 (same-side? vert-3 vert-1 vert-2 p)))) | |
151 | |
152 (defn triangle->matrix4f | |
153 "Converts the triangle into a 4x4 matrix: The first three columns | |
154 contain the vertices of the triangle; the last contains the unit | |
155 normal of the triangle. The bottom row is filled with 1s." | |
156 [#^Triangle t] | |
157 (let [mat (Matrix4f.) | |
158 [vert-1 vert-2 vert-3] | |
159 ((comp vec map) #(.get t %) (range 3)) | |
160 unit-normal (do (.calculateNormal t)(.getNormal t)) | |
161 vertices [vert-1 vert-2 vert-3 unit-normal]] | |
162 (dorun | |
163 (for [row (range 4) col (range 3)] | |
164 (do | |
165 (.set mat col row (.get (vertices row)col)) | |
166 (.set mat 3 row 1)))) | |
167 mat)) | |
168 | |
169 (defn triangle-transformation | |
170 "Returns the affine transformation that converts each vertex in the | |
171 first triangle into the corresponding vertex in the second | |
172 triangle." | |
173 [#^Triangle tri-1 #^Triangle tri-2] | |
174 (.mult | |
175 (triangle->matrix4f tri-2) | |
176 (.invert (triangle->matrix4f tri-1)))) | |
177 | |
178 (defn point->vector2f [[u v]] | |
179 (Vector2f. u v)) | |
180 | |
181 (defn vector2f->vector3f [v] | |
182 (Vector3f. (.getX v) (.getY v) 0)) | |
183 | |
184 (defn map-triangle [f #^Triangle tri] | |
185 (Triangle. | |
186 (f 0 (.get1 tri)) | |
187 (f 1 (.get2 tri)) | |
188 (f 2 (.get3 tri)))) | |
189 | |
190 (defn points->triangle | |
191 "Convert a list of points into a triangle." | |
192 [points] | |
193 (apply #(Triangle. %1 %2 %3) | |
194 (map (fn [point] | |
195 (let [point (vec point)] | |
196 (Vector3f. (get point 0 0) | |
197 (get point 1 0) | |
198 (get point 2 0)))) | |
199 (take 3 points)))) | |
200 | |
201 (defn convex-bounds | |
202 ;;dylan | |
203 "Returns the smallest square containing the given | |
204 vertices, as a vector of integers [left top width height]." | |
205 ;; "Dimensions of the smallest integer bounding square of the list of | |
206 ;; 2D verticies in the form: [x y width height]." | |
207 [uv-verts] | |
208 (let [xs (map first uv-verts) | |
209 ys (map second uv-verts) | |
210 x0 (Math/floor (apply min xs)) | |
211 y0 (Math/floor (apply min ys)) | |
212 x1 (Math/ceil (apply max xs)) | |
213 y1 (Math/ceil (apply max ys))] | |
214 [x0 y0 (- x1 x0) (- y1 y0)])) | |
215 | |
216 (defn sensors-in-triangle | |
217 ;;dylan | |
218 "Locate the touch sensors in the triangle, returning a map of their UV and geometry-relative coordinates." | |
219 ;;"Find the locations of the touch sensors within a triangle in both | |
220 ;; UV and gemoetry relative coordinates." | |
221 [image mesh tri-index] | |
222 (let [width (.getWidth image) | |
223 height (.getHeight image) | |
224 UV-vertex-coords (triangle-UV-coord mesh width height tri-index) | |
225 bounds (convex-bounds UV-vertex-coords) | |
226 | |
227 cutout-triangle (points->triangle UV-vertex-coords) | |
228 UV-sensor-coords | |
229 (filter (comp (partial inside-triangle? cutout-triangle) | |
230 (fn [[u v]] (Vector3f. u v 0))) | |
231 (white-coordinates image bounds)) | |
232 UV->geometry (triangle-transformation | |
233 cutout-triangle | |
234 (triangle mesh tri-index)) | |
235 geometry-sensor-coords | |
236 (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0))) | |
237 UV-sensor-coords)] | |
238 {:UV UV-sensor-coords :geometry geometry-sensor-coords})) | |
239 | |
240 (defn-memo locate-feelers | |
241 "Search the geometry's tactile UV image for touch sensors, returning | |
242 their positions in geometry-relative coordinates." | |
243 [#^Geometry geo] | |
244 (let [mesh (.getMesh geo) | |
245 num-triangles (.getTriangleCount mesh)] | |
246 (if-let [image (tactile-sensor-image geo)] | |
247 (map | |
248 (partial sensors-in-triangle image mesh) | |
249 (range num-triangles)) | |
250 (repeat (.getTriangleCount mesh) {:UV nil :geometry nil})))) | |
251 | |
252 (defn-memo touch-topology [#^Gemoetry geo] | |
253 (vec (collapse (reduce concat (map :UV (locate-feelers geo)))))) | |
254 | |
255 (defn-memo feeler-coordinates [#^Geometry geo] | |
256 (vec (map :geometry (locate-feelers geo)))) | |
257 | |
258 (defn enable-touch [#^Geometry geo] | |
259 (let [feeler-coords (feeler-coordinates geo) | |
260 tris (triangles geo) | |
261 limit 0.1 | |
262 ;;results (CollisionResults.) | |
263 ] | |
264 (if (empty? (touch-topology geo)) | |
265 nil | |
266 (fn [node] | |
267 (let [sensor-origins | |
268 (map | |
269 #(map (partial local-to-world geo) %) | |
270 feeler-coords) | |
271 triangle-normals | |
272 (map (partial get-ray-direction geo) | |
273 tris) | |
274 rays | |
275 (flatten | |
276 (map (fn [origins norm] | |
277 (map #(doto (Ray. % norm) | |
278 (.setLimit limit)) origins)) | |
279 sensor-origins triangle-normals))] | |
280 (vector | |
281 (touch-topology geo) | |
282 (vec | |
283 (for [ray rays] | |
284 (do | |
285 (let [results (CollisionResults.)] | |
286 (.collideWith node ray results) | |
287 (let [touch-objects | |
288 (filter #(not (= geo (.getGeometry %))) | |
289 results)] | |
290 (- 255 | |
291 (if (empty? touch-objects) 255 | |
292 (rem | |
293 (int | |
294 (* 255 (/ (.getDistance | |
295 (first touch-objects)) limit))) | |
296 256)))))))))))))) | |
297 | |
298 | |
299 (defn touch [#^Node pieces] | |
300 (filter (comp not nil?) | |
301 (map enable-touch | |
302 (filter #(isa? (class %) Geometry) | |
303 (node-seq pieces))))) | |
304 | |
305 | |
306 #+end_src | |
307 | |
308 | |
309 * Example | |
310 | |
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)) | |
323 | |
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)))) | |
335 | |
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)))))))) | |
348 | |
349 | |
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)) | |
361 | |
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)))) | |
366 | |
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 )))))) | |
379 | |
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)))))) | |
388 | |
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))) | |
405 | |
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))) | |
422 | |
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))) | |
433 | |
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 ] | |
449 | |
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 ) | |
461 | |
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 )))) | |
467 | |
468 | |
469 #+end_src | |
470 | |
471 | |
472 | |
473 | |
474 | |
475 * COMMENT code generation | |
476 #+begin_src clojure :tangle ../src/cortex/touch.clj | |
477 <<skin-main>> | |
478 #+end_src | |
479 | |
480 #+begin_src clojure :tangle ../src/cortex/test/touch.clj | |
481 <<touch-test>> | |
482 #+end_src | |
483 | |
484 | |
485 | |
486 | |
487 | |
488 |