annotate org/touch.org @ 244:f23217324f72

changed semantics of feeler lengths
author Robert McIntyre <rlm@mit.edu>
date Sun, 12 Feb 2012 14:29:09 -0700
parents f33fec68f775
children 102ac596cc3f
rev   line source
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@229 38 collision detections, one for each "hair". The placement of the
rlm@229 39 "hairs" is determined by a UV-mapped image which shows where each hair
rlm@229 40 should be on the 3D surface of the body.
rlm@228 41
rlm@229 42
rlm@229 43 * Defining Touch Meta-Data in Blender
rlm@229 44
rlm@229 45 Each geometry can have a single UV map which describes the position
rlm@229 46 and length of the "hairs" which will constitute its sense of
rlm@229 47 touch. This image path is stored under the "touch" key. The image
rlm@229 48 itself is grayscale, with black meaning a hair length of 0 (no hair is
rlm@229 49 present) and white meaning a hair length of =scale=, which is a float
rlm@229 50 stored under the key "scale". If the pixel is gray then the resultant
rlm@238 51 hair length is linearly interpolated between 0 and =scale=. I call
rlm@238 52 these "hairs" /feelers/.
rlm@229 53
rlm@231 54 #+name: meta-data
rlm@0 55 #+begin_src clojure
rlm@229 56 (defn tactile-sensor-profile
rlm@229 57 "Return the touch-sensor distribution image in BufferedImage format,
rlm@229 58 or nil if it does not exist."
rlm@229 59 [#^Geometry obj]
rlm@229 60 (if-let [image-path (meta-data obj "touch")]
rlm@229 61 (load-image image-path)))
rlm@233 62
rlm@233 63 (defn tactile-scale
rlm@233 64 "Return the maximum length of a hair. All hairs are scalled between
rlm@233 65 0.0 and this length, depending on their color. Black is 0, and
rlm@233 66 white is maximum length, and everything in between is scalled
rlm@233 67 linearlly. Default scale is 0.01 jMonkeyEngine units."
rlm@233 68 [#^Geometry obj]
rlm@233 69 (if-let [scale (meta-data obj "scale")]
rlm@233 70 scale 0.1))
rlm@228 71 #+end_src
rlm@156 72
rlm@229 73 ** TODO add image showing example touch-uv map
rlm@229 74 ** TODO add metadata display for worm
rlm@229 75
rlm@234 76
rlm@233 77 * Skin Creation
rlm@243 78 * TODO get the actual lengths for each feeler
rlm@234 79
rlm@238 80
rlm@238 81 =(touch-kernel)= generates the functions which implement the sense of
rlm@238 82 touch for a creature. These functions must do 6 things to obtain touch
rlm@238 83 data.
rlm@238 84
rlm@238 85 - Get the tactile profile image and scale paramaters which describe
rlm@238 86 the layout of feelers along the object's surface.
rlm@239 87 =(tactile-sensor-profile)=, =(tactile-scale)=
rlm@239 88
rlm@238 89 - Get the lengths of each feeler by analyzing the color of the
rlm@238 90 pixels in the tactile profile image.
rlm@239 91 NOT IMPLEMENTED YET
rlm@239 92
rlm@238 93 - Find the triangles which make up the mesh in pixel-space and in
rlm@238 94 world-space.
rlm@239 95 =(triangles)= =(pixel-triangles)=
rlm@239 96
rlm@239 97 - Find the coordinates of each pixel in pixel space. These
rlm@239 98 coordinates are used to make the touch-topology.
rlm@240 99 =(feeler-pixel-coords)=
rlm@239 100
rlm@238 101 - Find the coordinates of each pixel in world-space. These
rlm@240 102 coordinates are the origins of the feelers. =(feeler-origins)=
rlm@239 103
rlm@238 104 - Calculate the normals of the triangles in world space, and add
rlm@238 105 them to each of the origins of the feelers. These are the
rlm@238 106 normalized coordinates of the tips of the feelers.
rlm@240 107 For both of these, =(feeler-tips)=
rlm@239 108
rlm@238 109 - Generate some sort of topology for the sensors.
rlm@239 110 =(touch-topology)=
rlm@239 111
rlm@238 112
rlm@233 113 #+name: kernel
rlm@233 114 #+begin_src clojure
rlm@233 115 (in-ns 'cortex.touch)
rlm@233 116
rlm@239 117 (declare touch-topology feelers set-ray)
rlm@234 118
rlm@244 119 (defn set-ray [#^Ray ray #^Matrix4f transform
rlm@244 120 #^Vector3f origin #^Vector3f tip]
rlm@243 121 ;; Doing everything locally recduces garbage collection by enough to
rlm@243 122 ;; be worth it.
rlm@243 123 (.mult transform origin (.getOrigin ray))
rlm@243 124
rlm@243 125 (.mult transform tip (.getDirection ray))
rlm@244 126 (.subtractLocal (.getDirection ray) (.getOrigin ray)))
rlm@242 127
rlm@233 128 (defn touch-kernel
rlm@234 129 "Constructs a function which will return tactile sensory data from
rlm@234 130 'geo when called from inside a running simulation"
rlm@234 131 [#^Geometry geo]
rlm@243 132 (if-let
rlm@243 133 [profile (tactile-sensor-profile geo)]
rlm@243 134 (let [ray-reference-origins (feeler-origins geo profile)
rlm@243 135 ray-reference-tips (feeler-tips geo profile)
rlm@244 136 ray-length (tactile-scale geo)
rlm@243 137 current-rays (map (fn [_] (Ray.)) ray-reference-origins)
rlm@243 138 topology (touch-topology geo profile)]
rlm@244 139 (dorun (map #(.setLimit % ray-length) current-rays))
rlm@233 140 (fn [node]
rlm@243 141 (let [transform (.getWorldMatrix geo)]
rlm@243 142 (dorun
rlm@244 143 (map (fn [ray ref-origin ref-tip]
rlm@244 144 (set-ray ray transform ref-origin ref-tip))
rlm@243 145 current-rays ray-reference-origins
rlm@244 146 ray-reference-tips))
rlm@233 147 (vector
rlm@243 148 topology
rlm@233 149 (vec
rlm@243 150 (for [ray current-rays]
rlm@233 151 (do
rlm@233 152 (let [results (CollisionResults.)]
rlm@233 153 (.collideWith node ray results)
rlm@233 154 (let [touch-objects
rlm@233 155 (filter #(not (= geo (.getGeometry %)))
rlm@233 156 results)]
rlm@233 157 [(if (empty? touch-objects)
rlm@243 158 (.getLimit ray)
rlm@243 159 (.getDistance (first touch-objects)))
rlm@243 160 (.getLimit ray)])))))))))))
rlm@233 161
rlm@233 162 (defn touch!
rlm@233 163 "Endow the creature with the sense of touch. Returns a sequence of
rlm@233 164 functions, one for each body part with a tactile-sensor-proile,
rlm@233 165 each of which when called returns sensory data for that body part."
rlm@233 166 [#^Node creature]
rlm@233 167 (filter
rlm@233 168 (comp not nil?)
rlm@233 169 (map touch-kernel
rlm@233 170 (filter #(isa? (class %) Geometry)
rlm@233 171 (node-seq creature)))))
rlm@233 172 #+end_src
rlm@233 173
rlm@243 174 #+results: kernel
rlm@243 175 : #'cortex.touch/touch!
rlm@243 176
rlm@238 177 * Sensor Related Functions
rlm@238 178
rlm@238 179 These functions analyze the touch-sensor-profile image convert the
rlm@238 180 location of each touch sensor from pixel coordinates to UV-coordinates
rlm@238 181 and XYZ-coordinates.
rlm@238 182
rlm@238 183 #+name: sensors
rlm@238 184 #+begin_src clojure
rlm@240 185 (in-ns 'cortex.touch)
rlm@240 186
rlm@240 187 (defn feeler-pixel-coords
rlm@239 188 "Returns the coordinates of the feelers in pixel space in lists, one
rlm@239 189 list for each triangle, ordered in the same way as (triangles) and
rlm@239 190 (pixel-triangles)."
rlm@239 191 [#^Geometry geo image]
rlm@240 192 (map
rlm@240 193 (fn [pixel-triangle]
rlm@240 194 (filter
rlm@240 195 (fn [coord]
rlm@240 196 (inside-triangle? (->triangle pixel-triangle)
rlm@240 197 (->vector3f coord)))
rlm@240 198 (white-coordinates image (convex-bounds pixel-triangle))))
rlm@240 199 (pixel-triangles geo image)))
rlm@239 200
rlm@242 201 (defn feeler-world-coords [#^Geometry geo image]
rlm@240 202 (let [transforms
rlm@240 203 (map #(triangles->affine-transform
rlm@240 204 (->triangle %1) (->triangle %2))
rlm@240 205 (pixel-triangles geo image)
rlm@240 206 (triangles geo))]
rlm@242 207 (map (fn [transform coords]
rlm@240 208 (map #(.mult transform (->vector3f %)) coords))
rlm@240 209 transforms (feeler-pixel-coords geo image))))
rlm@239 210
rlm@242 211 (defn feeler-origins [#^Geometry geo image]
rlm@242 212 (reduce concat (feeler-world-coords geo image)))
rlm@242 213
rlm@240 214 (defn feeler-tips [#^Geometry geo image]
rlm@242 215 (let [world-coords (feeler-world-coords geo image)
rlm@241 216 normals
rlm@241 217 (map
rlm@241 218 (fn [triangle]
rlm@241 219 (.calculateNormal triangle)
rlm@241 220 (.clone (.getNormal triangle)))
rlm@241 221 (map ->triangle (triangles geo)))]
rlm@242 222
rlm@242 223 (mapcat (fn [origins normal]
rlm@242 224 (map #(.add % normal) origins))
rlm@242 225 world-coords normals)))
rlm@242 226
rlm@241 227
rlm@241 228 (defn touch-topology [#^Geometry geo image]
rlm@243 229 (collapse (reduce concat (feeler-pixel-coords geo image))))
rlm@238 230 #+end_src
rlm@238 231
rlm@233 232 * Visualizing Touch
rlm@233 233 #+name: visualization
rlm@233 234 #+begin_src clojure
rlm@233 235 (in-ns 'cortex.touch)
rlm@233 236
rlm@233 237 (defn touch->gray
rlm@233 238 "Convert a pair of [distance, max-distance] into a grayscale pixel"
rlm@233 239 [distance max-distance]
rlm@233 240 (gray
rlm@233 241 (- 255
rlm@233 242 (rem
rlm@233 243 (int
rlm@233 244 (* 255 (/ distance max-distance)))
rlm@233 245 256))))
rlm@233 246
rlm@233 247 (defn view-touch
rlm@233 248 "Creates a function which accepts a list of touch sensor-data and
rlm@233 249 displays each element to the screen."
rlm@233 250 []
rlm@233 251 (view-sense
rlm@233 252 (fn
rlm@233 253 [[coords sensor-data]]
rlm@233 254 (let [image (points->image coords)]
rlm@233 255 (dorun
rlm@233 256 (for [i (range (count coords))]
rlm@233 257 (.setRGB image ((coords i) 0) ((coords i) 1)
rlm@233 258 (apply touch->gray (sensor-data i)))))
rlm@233 259 image))))
rlm@233 260 #+end_src
rlm@233 261
rlm@233 262
rlm@233 263
rlm@228 264 * Triangle Manipulation Functions
rlm@228 265
rlm@229 266 The rigid bodies which make up a creature have an underlying
rlm@229 267 =Geometry=, which is a =Mesh= plus a =Material= and other important
rlm@229 268 data involved with displaying the body.
rlm@229 269
rlm@229 270 A =Mesh= is composed of =Triangles=, and each =Triangle= has three
rlm@229 271 verticies which have coordinates in XYZ space and UV space.
rlm@229 272
rlm@229 273 Here, =(triangles)= gets all the triangles which compose a mesh, and
rlm@229 274 =(triangle-UV-coord)= returns the the UV coordinates of the verticies
rlm@229 275 of a triangle.
rlm@229 276
rlm@231 277 #+name: triangles-1
rlm@228 278 #+begin_src clojure
rlm@239 279 (in-ns 'cortex.touch)
rlm@239 280
rlm@239 281 (defn vector3f-seq [#^Vector3f v]
rlm@239 282 [(.getX v) (.getY v) (.getZ v)])
rlm@239 283
rlm@239 284 (defn triangle-seq [#^Triangle tri]
rlm@239 285 [(vector3f-seq (.get1 tri))
rlm@239 286 (vector3f-seq (.get2 tri))
rlm@239 287 (vector3f-seq (.get3 tri))])
rlm@239 288
rlm@240 289 (defn ->vector3f
rlm@240 290 ([coords] (Vector3f. (nth coords 0 0)
rlm@240 291 (nth coords 1 0)
rlm@240 292 (nth coords 2 0))))
rlm@239 293
rlm@239 294 (defn ->triangle [points]
rlm@239 295 (apply #(Triangle. %1 %2 %3) (map ->vector3f points)))
rlm@239 296
rlm@239 297 (defn triangle
rlm@239 298 "Get the triangle specified by triangle-index from the mesh within
rlm@239 299 bounds."
rlm@239 300 [#^Geometry geo triangle-index]
rlm@239 301 (triangle-seq
rlm@239 302 (let [scratch (Triangle.)]
rlm@239 303 (.getTriangle (.getMesh geo) triangle-index scratch) scratch)))
rlm@239 304
rlm@228 305 (defn triangles
rlm@228 306 "Return a sequence of all the Triangles which compose a given
rlm@228 307 Geometry."
rlm@239 308 [#^Geometry geo]
rlm@239 309 (map (partial triangle geo) (range (.getTriangleCount (.getMesh geo)))))
rlm@228 310
rlm@228 311 (defn triangle-vertex-indices
rlm@228 312 "Get the triangle vertex indices of a given triangle from a given
rlm@228 313 mesh."
rlm@228 314 [#^Mesh mesh triangle-index]
rlm@228 315 (let [indices (int-array 3)]
rlm@228 316 (.getTriangle mesh triangle-index indices)
rlm@228 317 (vec indices)))
rlm@228 318
rlm@228 319 (defn vertex-UV-coord
rlm@228 320 "Get the UV-coordinates of the vertex named by vertex-index"
rlm@228 321 [#^Mesh mesh vertex-index]
rlm@228 322 (let [UV-buffer
rlm@228 323 (.getData
rlm@228 324 (.getBuffer
rlm@228 325 mesh
rlm@228 326 VertexBuffer$Type/TexCoord))]
rlm@228 327 [(.get UV-buffer (* vertex-index 2))
rlm@228 328 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
rlm@228 329
rlm@239 330 (defn pixel-triangle [#^Geometry geo image index]
rlm@239 331 (let [mesh (.getMesh geo)
rlm@239 332 width (.getWidth image)
rlm@239 333 height (.getHeight image)]
rlm@239 334 (vec (map (fn [[u v]] (vector (* width u) (* height v)))
rlm@239 335 (map (partial vertex-UV-coord mesh)
rlm@239 336 (triangle-vertex-indices mesh index))))))
rlm@228 337
rlm@239 338 (defn pixel-triangles [#^Geometry geo image]
rlm@239 339 (let [height (.getHeight image)
rlm@239 340 width (.getWidth image)]
rlm@239 341 (map (partial pixel-triangle geo image)
rlm@239 342 (range (.getTriangleCount (.getMesh geo))))))
rlm@229 343
rlm@228 344 #+end_src
rlm@228 345
rlm@228 346 * Triangle Affine Transforms
rlm@228 347
rlm@229 348 The position of each hair is stored in a 2D image in UV
rlm@229 349 coordinates. To place the hair in 3D space we must convert from UV
rlm@229 350 coordinates to XYZ coordinates. Each =Triangle= has coordinates in
rlm@229 351 both UV-space and XYZ-space, which defines a unique [[http://mathworld.wolfram.com/AffineTransformation.html ][Affine Transform]]
rlm@229 352 for translating any coordinate within the UV triangle to the
rlm@229 353 cooresponding coordinate in the XYZ triangle.
rlm@229 354
rlm@231 355 #+name: triangles-3
rlm@228 356 #+begin_src clojure
rlm@243 357 (in-ns 'cortex.touch)
rlm@243 358
rlm@228 359 (defn triangle->matrix4f
rlm@228 360 "Converts the triangle into a 4x4 matrix: The first three columns
rlm@228 361 contain the vertices of the triangle; the last contains the unit
rlm@228 362 normal of the triangle. The bottom row is filled with 1s."
rlm@228 363 [#^Triangle t]
rlm@228 364 (let [mat (Matrix4f.)
rlm@228 365 [vert-1 vert-2 vert-3]
rlm@228 366 ((comp vec map) #(.get t %) (range 3))
rlm@228 367 unit-normal (do (.calculateNormal t)(.getNormal t))
rlm@228 368 vertices [vert-1 vert-2 vert-3 unit-normal]]
rlm@228 369 (dorun
rlm@228 370 (for [row (range 4) col (range 3)]
rlm@228 371 (do
rlm@228 372 (.set mat col row (.get (vertices row)col))
rlm@228 373 (.set mat 3 row 1))))
rlm@228 374 mat))
rlm@228 375
rlm@240 376 (defn triangles->affine-transform
rlm@228 377 "Returns the affine transformation that converts each vertex in the
rlm@228 378 first triangle into the corresponding vertex in the second
rlm@228 379 triangle."
rlm@228 380 [#^Triangle tri-1 #^Triangle tri-2]
rlm@228 381 (.mult
rlm@228 382 (triangle->matrix4f tri-2)
rlm@228 383 (.invert (triangle->matrix4f tri-1))))
rlm@228 384 #+end_src
rlm@228 385
rlm@239 386
rlm@239 387 * Schrapnel Conversion Functions
rlm@239 388
rlm@239 389 It is convienent to treat a =Triangle= as a sequence of verticies, and
rlm@239 390 a =Vector2f= and =Vector3f= as a sequence of floats. These conversion
rlm@239 391 functions make this easy. If these classes implemented =Iterable= then
rlm@239 392 this code would not be necessary. Hopefully they will in the future.
rlm@239 393
rlm@239 394
rlm@229 395 * Triangle Boundaries
rlm@229 396
rlm@229 397 For efficiency's sake I will divide the UV-image into small squares
rlm@229 398 which inscribe each UV-triangle, then extract the points which lie
rlm@229 399 inside the triangle and map them to 3D-space using
rlm@229 400 =(triangle-transform)= above. To do this I need a function,
rlm@229 401 =(inside-triangle?)=, which determines whether a point is inside a
rlm@229 402 triangle in 2D UV-space.
rlm@228 403
rlm@231 404 #+name: triangles-4
rlm@228 405 #+begin_src clojure
rlm@229 406 (defn convex-bounds
rlm@229 407 "Returns the smallest square containing the given vertices, as a
rlm@229 408 vector of integers [left top width height]."
rlm@240 409 [verts]
rlm@240 410 (let [xs (map first verts)
rlm@240 411 ys (map second verts)
rlm@229 412 x0 (Math/floor (apply min xs))
rlm@229 413 y0 (Math/floor (apply min ys))
rlm@229 414 x1 (Math/ceil (apply max xs))
rlm@229 415 y1 (Math/ceil (apply max ys))]
rlm@229 416 [x0 y0 (- x1 x0) (- y1 y0)]))
rlm@229 417
rlm@229 418 (defn same-side?
rlm@229 419 "Given the points p1 and p2 and the reference point ref, is point p
rlm@229 420 on the same side of the line that goes through p1 and p2 as ref is?"
rlm@229 421 [p1 p2 ref p]
rlm@229 422 (<=
rlm@229 423 0
rlm@229 424 (.dot
rlm@229 425 (.cross (.subtract p2 p1) (.subtract p p1))
rlm@229 426 (.cross (.subtract p2 p1) (.subtract ref p1)))))
rlm@229 427
rlm@229 428 (defn inside-triangle?
rlm@229 429 "Is the point inside the triangle?"
rlm@229 430 {:author "Dylan Holmes"}
rlm@229 431 [#^Triangle tri #^Vector3f p]
rlm@240 432 (let [[vert-1 vert-2 vert-3] [(.get1 tri) (.get2 tri) (.get3 tri)]]
rlm@229 433 (and
rlm@229 434 (same-side? vert-1 vert-2 vert-3 p)
rlm@229 435 (same-side? vert-2 vert-3 vert-1 p)
rlm@229 436 (same-side? vert-3 vert-1 vert-2 p))))
rlm@229 437 #+end_src
rlm@229 438
rlm@228 439 * Physics Collision Objects
rlm@230 440
rlm@234 441 The "hairs" are actually =Rays= which extend from a point on a
rlm@230 442 =Triangle= in the =Mesh= normal to the =Triangle's= surface.
rlm@230 443
rlm@226 444 * Headers
rlm@231 445
rlm@231 446 #+name: touch-header
rlm@226 447 #+begin_src clojure
rlm@226 448 (ns cortex.touch
rlm@226 449 "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry
rlm@226 450 to be outfitted with touch sensors with density determined by a UV
rlm@226 451 image. In this way a Geometry can know what parts of itself are
rlm@226 452 touching nearby objects. Reads specially prepared blender files to
rlm@226 453 construct this sense automatically."
rlm@226 454 {:author "Robert McIntyre"}
rlm@226 455 (:use (cortex world util sense))
rlm@226 456 (:use clojure.contrib.def)
rlm@226 457 (:import (com.jme3.scene Geometry Node Mesh))
rlm@226 458 (:import com.jme3.collision.CollisionResults)
rlm@226 459 (:import com.jme3.scene.VertexBuffer$Type)
rlm@226 460 (:import (com.jme3.math Triangle Vector3f Vector2f Ray Matrix4f)))
rlm@226 461 #+end_src
rlm@37 462
rlm@232 463 * Adding Touch to the Worm
rlm@232 464
rlm@232 465 #+name: test-touch
rlm@232 466 #+begin_src clojure
rlm@232 467 (ns cortex.test.touch
rlm@232 468 (:use (cortex world util sense body touch))
rlm@232 469 (:use cortex.test.body))
rlm@232 470
rlm@232 471 (cortex.import/mega-import-jme3)
rlm@232 472
rlm@232 473 (defn test-touch []
rlm@232 474 (let [the-worm (doto (worm) (body!))
rlm@232 475 touch (touch! the-worm)
rlm@232 476 touch-display (view-touch)]
rlm@232 477 (world (nodify [the-worm (floor)])
rlm@232 478 standard-debug-controls
rlm@232 479
rlm@232 480 (fn [world]
rlm@244 481 (speed-up world)
rlm@232 482 (light-up-everything world))
rlm@232 483
rlm@232 484 (fn [world tpf]
rlm@244 485 (touch-display (map #(% (.getRootNode world)) touch))
rlm@243 486 ))))
rlm@232 487 #+end_src
rlm@228 488 * Source Listing
rlm@228 489 * Next
rlm@228 490
rlm@228 491
rlm@226 492 * COMMENT Code Generation
rlm@39 493 #+begin_src clojure :tangle ../src/cortex/touch.clj
rlm@231 494 <<touch-header>>
rlm@231 495 <<meta-data>>
rlm@231 496 <<triangles-1>>
rlm@231 497 <<triangles-3>>
rlm@231 498 <<triangles-4>>
rlm@231 499 <<sensors>>
rlm@231 500 <<kernel>>
rlm@231 501 <<visualization>>
rlm@0 502 #+end_src
rlm@0 503
rlm@232 504
rlm@68 505 #+begin_src clojure :tangle ../src/cortex/test/touch.clj
rlm@232 506 <<test-touch>>
rlm@39 507 #+end_src
rlm@39 508
rlm@0 509
rlm@0 510
rlm@0 511
rlm@32 512
rlm@32 513
rlm@226 514