Mercurial > cortex
diff 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 |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/org/touch.org Sat Feb 04 06:54:14 2012 -0700 1.3 @@ -0,0 +1,488 @@ 1.4 +#+title: Simulated Sense of Touch 1.5 +#+author: Robert McIntyre 1.6 +#+email: rlm@mit.edu 1.7 +#+description: Simulated touch for AI research using JMonkeyEngine and clojure. 1.8 +#+keywords: simulation, tactile sense, jMonkeyEngine3, clojure 1.9 +#+SETUPFILE: ../../aurellem/org/setup.org 1.10 +#+INCLUDE: ../../aurellem/org/level-0.org 1.11 + 1.12 + 1.13 +* Touch 1.14 + 1.15 +My creatures need to be able to feel their environments. The idea here 1.16 +is to create thousands of small /touch receptors/ along the geometries 1.17 +which make up the creature's body. The number of touch receptors in a 1.18 +given area is determined by how complicated that area is, as 1.19 +determined by the total number of triangles in that region. This way, 1.20 +complicated regions like the hands/face, etc. get more touch receptors 1.21 +than simpler areas of the body. 1.22 + 1.23 +#+name: skin-main 1.24 +#+begin_src clojure 1.25 +(ns cortex.touch 1.26 + "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry 1.27 + to be outfitted with touch sensors with density proportional to the 1.28 + density of triangles along the surface of the Geometry. Enables a 1.29 + Geometry to know what parts of itself are touching nearby objects." 1.30 + {:author "Robert McIntyre"} 1.31 + (:use (cortex world util sense)) 1.32 + (:import com.jme3.scene.Geometry) 1.33 + (:import com.jme3.collision.CollisionResults) 1.34 + (:import jme3tools.converters.ImageToAwt) 1.35 + (:import (com.jme3.math Triangle Vector3f Ray))) 1.36 + 1.37 +(use 'clojure.contrib.def) 1.38 +(cortex.import/mega-import-jme3) 1.39 + 1.40 +(defn triangles 1.41 + "Return a sequence of all the Triangles which compose a given 1.42 + Geometry." 1.43 + [#^Geometry geom] 1.44 + (let 1.45 + [mesh (.getMesh geom) 1.46 + triangles (transient [])] 1.47 + (dorun 1.48 + (for [n (range (.getTriangleCount mesh))] 1.49 + (let [tri (Triangle.)] 1.50 + (.getTriangle mesh n tri) 1.51 + ;; (.calculateNormal tri) 1.52 + ;; (.calculateCenter tri) 1.53 + (conj! triangles tri)))) 1.54 + (persistent! triangles))) 1.55 + 1.56 +(defn get-ray-origin 1.57 + "Return the origin which a Ray would have to have to be in the exact 1.58 + center of a particular Triangle in the Geometry in World 1.59 + Coordinates." 1.60 + [geom tri] 1.61 + (let [new (Vector3f.)] 1.62 + (.calculateCenter tri) 1.63 + (.localToWorld geom (.getCenter tri) new) new)) 1.64 + 1.65 +(defn get-ray-direction 1.66 + "Return the direction which a Ray would have to have to be to point 1.67 + normal to the Triangle, in coordinates relative to the center of the 1.68 + Triangle." 1.69 + [geom tri] 1.70 + (let [n+c (Vector3f.)] 1.71 + (.calculateNormal tri) 1.72 + (.calculateCenter tri) 1.73 + (.localToWorld 1.74 + geom 1.75 + (.add (.getCenter tri) (.getNormal tri)) n+c) 1.76 + (.subtract n+c (get-ray-origin geom tri)))) 1.77 + 1.78 +;; Every Mesh has many triangles, each with its own index. 1.79 +;; Every vertex has its own index as well. 1.80 + 1.81 +(defn tactile-sensor-image 1.82 + "Return the touch-sensor distribution image in BufferedImage format, 1.83 + or nil if it does not exist." 1.84 + [#^Geometry obj] 1.85 + (if-let [image-path (meta-data obj "touch")] 1.86 + (ImageToAwt/convert 1.87 + (.getImage 1.88 + (.loadTexture 1.89 + (asset-manager) 1.90 + image-path)) 1.91 + false false 0))) 1.92 + 1.93 + 1.94 + 1.95 +(defn triangle 1.96 + "Get the triangle specified by triangle-index from the mesh within 1.97 + bounds." 1.98 + [#^Mesh mesh triangle-index] 1.99 + (let [scratch (Triangle.)] 1.100 + (.getTriangle mesh triangle-index scratch) 1.101 + scratch)) 1.102 + 1.103 +(defn triangle-vertex-indices 1.104 + "Get the triangle vertex indices of a given triangle from a given 1.105 + mesh." 1.106 + [#^Mesh mesh triangle-index] 1.107 + (let [indices (int-array 3)] 1.108 + (.getTriangle mesh triangle-index indices) 1.109 + (vec indices))) 1.110 + 1.111 +(defn vertex-UV-coord 1.112 + "Get the uv-coordinates of the vertex named by vertex-index" 1.113 + [#^Mesh mesh vertex-index] 1.114 + (let [UV-buffer 1.115 + (.getData 1.116 + (.getBuffer 1.117 + mesh 1.118 + VertexBuffer$Type/TexCoord))] 1.119 + [(.get UV-buffer (* vertex-index 2)) 1.120 + (.get UV-buffer (+ 1 (* vertex-index 2)))])) 1.121 + 1.122 +(defn triangle-UV-coord 1.123 + "Get the uv-cooridnates of the triangle's verticies." 1.124 + [#^Mesh mesh width height triangle-index] 1.125 + (map (fn [[u v]] (vector (* width u) (* height v))) 1.126 + (map (partial vertex-UV-coord mesh) 1.127 + (triangle-vertex-indices mesh triangle-index)))) 1.128 + 1.129 +(defn same-side? 1.130 + "Given the points p1 and p2 and the reference point ref, is point p 1.131 + on the same side of the line that goes through p1 and p2 as ref is?" 1.132 + [p1 p2 ref p] 1.133 + (<= 1.134 + 0 1.135 + (.dot 1.136 + (.cross (.subtract p2 p1) (.subtract p p1)) 1.137 + (.cross (.subtract p2 p1) (.subtract ref p1))))) 1.138 + 1.139 +(defn triangle-seq [#^Triangle tri] 1.140 + [(.get1 tri) (.get2 tri) (.get3 tri)]) 1.141 + 1.142 +(defn vector3f-seq [#^Vector3f v] 1.143 + [(.getX v) (.getY v) (.getZ v)]) 1.144 + 1.145 +(defn inside-triangle? 1.146 + "Is the point inside the triangle?" 1.147 + {:author "Dylan Holmes"} 1.148 + [#^Triangle tri #^Vector3f p] 1.149 + (let [[vert-1 vert-2 vert-3] (triangle-seq tri)] 1.150 + (and 1.151 + (same-side? vert-1 vert-2 vert-3 p) 1.152 + (same-side? vert-2 vert-3 vert-1 p) 1.153 + (same-side? vert-3 vert-1 vert-2 p)))) 1.154 + 1.155 +(defn triangle->matrix4f 1.156 + "Converts the triangle into a 4x4 matrix: The first three columns 1.157 + contain the vertices of the triangle; the last contains the unit 1.158 + normal of the triangle. The bottom row is filled with 1s." 1.159 + [#^Triangle t] 1.160 + (let [mat (Matrix4f.) 1.161 + [vert-1 vert-2 vert-3] 1.162 + ((comp vec map) #(.get t %) (range 3)) 1.163 + unit-normal (do (.calculateNormal t)(.getNormal t)) 1.164 + vertices [vert-1 vert-2 vert-3 unit-normal]] 1.165 + (dorun 1.166 + (for [row (range 4) col (range 3)] 1.167 + (do 1.168 + (.set mat col row (.get (vertices row)col)) 1.169 + (.set mat 3 row 1)))) 1.170 + mat)) 1.171 + 1.172 +(defn triangle-transformation 1.173 + "Returns the affine transformation that converts each vertex in the 1.174 + first triangle into the corresponding vertex in the second 1.175 + triangle." 1.176 + [#^Triangle tri-1 #^Triangle tri-2] 1.177 + (.mult 1.178 + (triangle->matrix4f tri-2) 1.179 + (.invert (triangle->matrix4f tri-1)))) 1.180 + 1.181 +(defn point->vector2f [[u v]] 1.182 + (Vector2f. u v)) 1.183 + 1.184 +(defn vector2f->vector3f [v] 1.185 + (Vector3f. (.getX v) (.getY v) 0)) 1.186 + 1.187 +(defn map-triangle [f #^Triangle tri] 1.188 + (Triangle. 1.189 + (f 0 (.get1 tri)) 1.190 + (f 1 (.get2 tri)) 1.191 + (f 2 (.get3 tri)))) 1.192 + 1.193 +(defn points->triangle 1.194 + "Convert a list of points into a triangle." 1.195 + [points] 1.196 + (apply #(Triangle. %1 %2 %3) 1.197 + (map (fn [point] 1.198 + (let [point (vec point)] 1.199 + (Vector3f. (get point 0 0) 1.200 + (get point 1 0) 1.201 + (get point 2 0)))) 1.202 + (take 3 points)))) 1.203 + 1.204 +(defn convex-bounds 1.205 + ;;dylan 1.206 + "Returns the smallest square containing the given 1.207 +vertices, as a vector of integers [left top width height]." 1.208 + ;; "Dimensions of the smallest integer bounding square of the list of 1.209 + ;; 2D verticies in the form: [x y width height]." 1.210 + [uv-verts] 1.211 + (let [xs (map first uv-verts) 1.212 + ys (map second uv-verts) 1.213 + x0 (Math/floor (apply min xs)) 1.214 + y0 (Math/floor (apply min ys)) 1.215 + x1 (Math/ceil (apply max xs)) 1.216 + y1 (Math/ceil (apply max ys))] 1.217 + [x0 y0 (- x1 x0) (- y1 y0)])) 1.218 + 1.219 +(defn sensors-in-triangle 1.220 + ;;dylan 1.221 + "Locate the touch sensors in the triangle, returning a map of their UV and geometry-relative coordinates." 1.222 + ;;"Find the locations of the touch sensors within a triangle in both 1.223 + ;; UV and gemoetry relative coordinates." 1.224 + [image mesh tri-index] 1.225 + (let [width (.getWidth image) 1.226 + height (.getHeight image) 1.227 + UV-vertex-coords (triangle-UV-coord mesh width height tri-index) 1.228 + bounds (convex-bounds UV-vertex-coords) 1.229 + 1.230 + cutout-triangle (points->triangle UV-vertex-coords) 1.231 + UV-sensor-coords 1.232 + (filter (comp (partial inside-triangle? cutout-triangle) 1.233 + (fn [[u v]] (Vector3f. u v 0))) 1.234 + (white-coordinates image bounds)) 1.235 + UV->geometry (triangle-transformation 1.236 + cutout-triangle 1.237 + (triangle mesh tri-index)) 1.238 + geometry-sensor-coords 1.239 + (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0))) 1.240 + UV-sensor-coords)] 1.241 + {:UV UV-sensor-coords :geometry geometry-sensor-coords})) 1.242 + 1.243 +(defn-memo locate-feelers 1.244 + "Search the geometry's tactile UV image for touch sensors, returning 1.245 + their positions in geometry-relative coordinates." 1.246 + [#^Geometry geo] 1.247 + (let [mesh (.getMesh geo) 1.248 + num-triangles (.getTriangleCount mesh)] 1.249 + (if-let [image (tactile-sensor-image geo)] 1.250 + (map 1.251 + (partial sensors-in-triangle image mesh) 1.252 + (range num-triangles)) 1.253 + (repeat (.getTriangleCount mesh) {:UV nil :geometry nil})))) 1.254 + 1.255 +(defn-memo touch-topology [#^Gemoetry geo] 1.256 + (vec (collapse (reduce concat (map :UV (locate-feelers geo)))))) 1.257 + 1.258 +(defn-memo feeler-coordinates [#^Geometry geo] 1.259 + (vec (map :geometry (locate-feelers geo)))) 1.260 + 1.261 +(defn enable-touch [#^Geometry geo] 1.262 + (let [feeler-coords (feeler-coordinates geo) 1.263 + tris (triangles geo) 1.264 + limit 0.1 1.265 + ;;results (CollisionResults.) 1.266 + ] 1.267 + (if (empty? (touch-topology geo)) 1.268 + nil 1.269 + (fn [node] 1.270 + (let [sensor-origins 1.271 + (map 1.272 + #(map (partial local-to-world geo) %) 1.273 + feeler-coords) 1.274 + triangle-normals 1.275 + (map (partial get-ray-direction geo) 1.276 + tris) 1.277 + rays 1.278 + (flatten 1.279 + (map (fn [origins norm] 1.280 + (map #(doto (Ray. % norm) 1.281 + (.setLimit limit)) origins)) 1.282 + sensor-origins triangle-normals))] 1.283 + (vector 1.284 + (touch-topology geo) 1.285 + (vec 1.286 + (for [ray rays] 1.287 + (do 1.288 + (let [results (CollisionResults.)] 1.289 + (.collideWith node ray results) 1.290 + (let [touch-objects 1.291 + (filter #(not (= geo (.getGeometry %))) 1.292 + results)] 1.293 + (- 255 1.294 + (if (empty? touch-objects) 255 1.295 + (rem 1.296 + (int 1.297 + (* 255 (/ (.getDistance 1.298 + (first touch-objects)) limit))) 1.299 + 256)))))))))))))) 1.300 + 1.301 + 1.302 +(defn touch [#^Node pieces] 1.303 + (filter (comp not nil?) 1.304 + (map enable-touch 1.305 + (filter #(isa? (class %) Geometry) 1.306 + (node-seq pieces))))) 1.307 + 1.308 + 1.309 +#+end_src 1.310 + 1.311 + 1.312 +* Example 1.313 + 1.314 +#+name: touch-test 1.315 +#+begin_src clojure 1.316 +(ns cortex.test.touch 1.317 + (:use (cortex world util touch)) 1.318 + (:import 1.319 + com.jme3.scene.shape.Sphere 1.320 + com.jme3.math.ColorRGBA 1.321 + com.jme3.math.Vector3f 1.322 + com.jme3.material.RenderState$BlendMode 1.323 + com.jme3.renderer.queue.RenderQueue$Bucket 1.324 + com.jme3.scene.shape.Box 1.325 + com.jme3.scene.Node)) 1.326 + 1.327 +(defn ray-origin-debug 1.328 + [ray color] 1.329 + (make-shape 1.330 + (assoc base-shape 1.331 + :shape (Sphere. 5 5 0.05) 1.332 + :name "arrow" 1.333 + :color color 1.334 + :texture false 1.335 + :physical? false 1.336 + :position 1.337 + (.getOrigin ray)))) 1.338 + 1.339 +(defn ray-debug [ray color] 1.340 + (make-shape 1.341 + (assoc 1.342 + base-shape 1.343 + :name "debug-ray" 1.344 + :physical? false 1.345 + :shape (com.jme3.scene.shape.Line. 1.346 + (.getOrigin ray) 1.347 + (.add 1.348 + (.getOrigin ray) 1.349 + (.mult (.getDirection ray) 1.350 + (float (.getLimit ray)))))))) 1.351 + 1.352 + 1.353 +(defn contact-color [contacts] 1.354 + (case contacts 1.355 + 0 ColorRGBA/Gray 1.356 + 1 ColorRGBA/Red 1.357 + 2 ColorRGBA/Green 1.358 + 3 ColorRGBA/Yellow 1.359 + 4 ColorRGBA/Orange 1.360 + 5 ColorRGBA/Red 1.361 + 6 ColorRGBA/Magenta 1.362 + 7 ColorRGBA/Pink 1.363 + 8 ColorRGBA/White)) 1.364 + 1.365 +(defn update-ray-debug [node ray contacts] 1.366 + (let [origin (.getChild node 0)] 1.367 + (.setLocalTranslation origin (.getOrigin ray)) 1.368 + (.setColor (.getMaterial origin) "Color" (contact-color contacts)))) 1.369 + 1.370 +(defn init-node 1.371 + [debug-node rays] 1.372 + (.detachAllChildren debug-node) 1.373 + (dorun 1.374 + (for [ray rays] 1.375 + (do 1.376 + (.attachChild 1.377 + debug-node 1.378 + (doto (Node.) 1.379 + (.attachChild (ray-origin-debug ray ColorRGBA/Gray)) 1.380 + (.attachChild (ray-debug ray ColorRGBA/Gray)) 1.381 + )))))) 1.382 + 1.383 +(defn manage-ray-debug-node [debug-node geom touch-data limit] 1.384 + (let [rays (normal-rays limit geom)] 1.385 + (if (not= (count (.getChildren debug-node)) (count touch-data)) 1.386 + (init-node debug-node rays)) 1.387 + (dorun 1.388 + (for [n (range (count touch-data))] 1.389 + (update-ray-debug 1.390 + (.getChild debug-node n) (nth rays n) (nth touch-data n)))))) 1.391 + 1.392 +(defn transparent-sphere [] 1.393 + (doto 1.394 + (make-shape 1.395 + (merge base-shape 1.396 + {:position (Vector3f. 0 2 0) 1.397 + :name "the blob." 1.398 + :material "Common/MatDefs/Misc/Unshaded.j3md" 1.399 + :texture "Textures/purpleWisp.png" 1.400 + :physical? true 1.401 + :mass 70 1.402 + :color ColorRGBA/Blue 1.403 + :shape (Sphere. 10 10 1)})) 1.404 + (-> (.getMaterial) 1.405 + (.getAdditionalRenderState) 1.406 + (.setBlendMode RenderState$BlendMode/Alpha)) 1.407 + (.setQueueBucket RenderQueue$Bucket/Transparent))) 1.408 + 1.409 +(defn transparent-box [] 1.410 + (doto 1.411 + (make-shape 1.412 + (merge base-shape 1.413 + {:position (Vector3f. 0 2 0) 1.414 + :name "box" 1.415 + :material "Common/MatDefs/Misc/Unshaded.j3md" 1.416 + :texture "Textures/purpleWisp.png" 1.417 + :physical? true 1.418 + :mass 70 1.419 + :color ColorRGBA/Blue 1.420 + :shape (Box. 1 1 1)})) 1.421 + (-> (.getMaterial) 1.422 + (.getAdditionalRenderState) 1.423 + (.setBlendMode RenderState$BlendMode/Alpha)) 1.424 + (.setQueueBucket RenderQueue$Bucket/Transparent))) 1.425 + 1.426 +(defn transparent-floor [] 1.427 + (doto 1.428 + (box 5 0.2 5 :mass 0 :position (Vector3f. 0 -2 0) 1.429 + :material "Common/MatDefs/Misc/Unshaded.j3md" 1.430 + :texture "Textures/redWisp.png" 1.431 + :name "floor") 1.432 + (-> (.getMaterial) 1.433 + (.getAdditionalRenderState) 1.434 + (.setBlendMode RenderState$BlendMode/Alpha)) 1.435 + (.setQueueBucket RenderQueue$Bucket/Transparent))) 1.436 + 1.437 +(defn test-skin 1.438 + "Testing touch: 1.439 + you should see a ball which responds to the table 1.440 + and whatever balls hit it." 1.441 + [] 1.442 + (let [b 1.443 + ;;(transparent-box) 1.444 + (transparent-sphere) 1.445 + ;;(sphere) 1.446 + f (transparent-floor) 1.447 + debug-node (Node.) 1.448 + node (doto (Node.) (.attachChild b) (.attachChild f)) 1.449 + root-node (doto (Node.) (.attachChild node) 1.450 + (.attachChild debug-node)) 1.451 + ] 1.452 + 1.453 + (world 1.454 + root-node 1.455 + {"key-return" (fire-cannon-ball node)} 1.456 + (fn [world] 1.457 + ;; (Capture/SimpleCaptureVideo 1.458 + ;; world 1.459 + ;; (file-str "/home/r/proj/cortex/tmp/blob.avi")) 1.460 + ;; (no-logging) 1.461 + ;;(enable-debug world) 1.462 + ;; (set-accuracy world (/ 1 60)) 1.463 + ) 1.464 + 1.465 + (fn [& _] 1.466 + (let [sensitivity 0.2 1.467 + touch-data (touch-percieve sensitivity b node)] 1.468 + (manage-ray-debug-node debug-node b touch-data sensitivity)) 1.469 + )))) 1.470 + 1.471 + 1.472 +#+end_src 1.473 + 1.474 + 1.475 + 1.476 + 1.477 + 1.478 +* COMMENT code generation 1.479 +#+begin_src clojure :tangle ../src/cortex/touch.clj 1.480 +<<skin-main>> 1.481 +#+end_src 1.482 + 1.483 +#+begin_src clojure :tangle ../src/cortex/test/touch.clj 1.484 +<<touch-test>> 1.485 +#+end_src 1.486 + 1.487 + 1.488 + 1.489 + 1.490 + 1.491 +