annotate org/touch.org @ 233:f27c9fd9134d

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