view org/test-creature.org @ 123:91773e8ec50f

got hearing diaplay working
author Robert McIntyre <rlm@mit.edu>
date Mon, 23 Jan 2012 03:41:26 -0700
parents b591da250afc
children 90154bd674e9
line wrap: on
line source
1 #+title: First attempt at a creature!
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description:
5 #+keywords: simulation, jMonkeyEngine3, clojure
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
9 * objectives
10 - [X] get an overall bitmap-like image for touch
11 - [X] write code to visuliaze this bitmap
12 - [ ] directly change the UV-pixels to show touch sensor activation
13 - [ ] write an explination for why b&w bitmaps for senses is appropiate
14 - [ ] clean up touch code and write visulazation test
15 - [ ] do the same for eyes
17 * Intro
18 So far, I've made the following senses --
19 - Vision
20 - Hearing
21 - Touch
22 - Proprioception
24 And one effector:
25 - Movement
27 However, the code so far has only enabled these senses, but has not
28 actually implemented them. For example, there is still a lot of work
29 to be done for vision. I need to be able to create an /eyeball/ in
30 simulation that can be moved around and see the world from different
31 angles. I also need to determine weather to use log-polar or cartesian
32 for the visual input, and I need to determine how/wether to
33 disceritise the visual input.
35 I also want to be able to visualize both the sensors and the
36 effectors in pretty pictures. This semi-retarted creature will be my
37 first attempt at bringing everything together.
39 * The creature's body
41 Still going to do an eve-like body in blender, but due to problems
42 importing the joints, etc into jMonkeyEngine3, I'm going to do all
43 the connecting here in clojure code, using the names of the individual
44 components and trial and error. Later, I'll maybe make some sort of
45 creature-building modifications to blender that support whatever
46 discreitized senses I'm going to make.
48 #+name: body-1
49 #+begin_src clojure
50 (ns cortex.silly
51 "let's play!"
52 {:author "Robert McIntyre"})
54 ;; TODO remove this!
55 (require 'cortex.import)
56 (cortex.import/mega-import-jme3)
57 (use '(cortex world util body hearing touch vision))
59 (rlm.rlm-commands/help)
60 (import java.awt.image.BufferedImage)
61 (import javax.swing.JPanel)
62 (import javax.swing.SwingUtilities)
63 (import java.awt.Dimension)
64 (import javax.swing.JFrame)
65 (import java.awt.Dimension)
66 (import com.aurellem.capture.RatchetTimer)
67 (declare joint-create)
68 (use 'clojure.contrib.def)
70 (defn points->image
71 "Take a sparse collection of points and visuliaze it as a
72 BufferedImage."
74 ;; TODO maybe parallelize this since it's easy
76 [points]
77 (if (empty? points)
78 (BufferedImage. 1 1 BufferedImage/TYPE_BYTE_BINARY)
79 (let [xs (vec (map first points))
80 ys (vec (map second points))
81 x0 (apply min xs)
82 y0 (apply min ys)
83 width (- (apply max xs) x0)
84 height (- (apply max ys) y0)
85 image (BufferedImage. (inc width) (inc height)
86 BufferedImage/TYPE_INT_RGB)]
87 (dorun
88 (for [x (range (.getWidth image))
89 y (range (.getHeight image))]
90 (.setRGB image x y 0xFF0000)))
91 (dorun
92 (for [index (range (count points))]
93 (.setRGB image (- (xs index) x0) (- (ys index) y0) -1)))
95 image)))
97 (defn average [coll]
98 (/ (reduce + coll) (count coll)))
100 (defn collapse-1d
101 "One dimensional analogue of collapse"
102 [center line]
103 (let [length (count line)
104 num-above (count (filter (partial < center) line))
105 num-below (- length num-above)]
106 (range (- center num-below)
107 (+ center num-above))))
109 (defn collapse
110 "Take a set of pairs of integers and collapse them into a
111 contigous bitmap."
112 [points]
113 (if (empty? points) []
114 (let
115 [num-points (count points)
116 center (vector
117 (int (average (map first points)))
118 (int (average (map first points))))
119 flattened
120 (reduce
121 concat
122 (map
123 (fn [column]
124 (map vector
125 (map first column)
126 (collapse-1d (second center)
127 (map second column))))
128 (partition-by first (sort-by first points))))
129 squeezed
130 (reduce
131 concat
132 (map
133 (fn [row]
134 (map vector
135 (collapse-1d (first center)
136 (map first row))
137 (map second row)))
138 (partition-by second (sort-by second flattened))))
139 relocate
140 (let [min-x (apply min (map first squeezed))
141 min-y (apply min (map second squeezed))]
142 (map (fn [[x y]]
143 [(- x min-x)
144 (- y min-y)])
145 squeezed))]
146 relocate)))
148 (defn load-bullet []
149 (let [sim (world (Node.) {} no-op no-op)]
150 (doto sim
151 (.enqueue
152 (fn []
153 (.stop sim)))
154 (.start))))
156 (defn load-blender-model
157 "Load a .blend file using an asset folder relative path."
158 [^String model]
159 (.loadModel
160 (doto (asset-manager)
161 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
162 model))
164 (defn meta-data [blender-node key]
165 (if-let [data (.getUserData blender-node "properties")]
166 (.findValue data key)
167 nil))
169 (defn blender-to-jme
170 "Convert from Blender coordinates to JME coordinates"
171 [#^Vector3f in]
172 (Vector3f. (.getX in)
173 (.getZ in)
174 (- (.getY in))))
176 (defn jme-to-blender
177 "Convert from JME coordinates to Blender coordinates"
178 [#^Vector3f in]
179 (Vector3f. (.getX in)
180 (- (.getZ in))
181 (.getY in)))
183 (defn joint-targets
184 "Return the two closest two objects to the joint object, ordered
185 from bottom to top according to the joint's rotation."
186 [#^Node parts #^Node joint]
187 (loop [radius (float 0.01)]
188 (let [results (CollisionResults.)]
189 (.collideWith
190 parts
191 (BoundingBox. (.getWorldTranslation joint)
192 radius radius radius)
193 results)
194 (let [targets
195 (distinct
196 (map #(.getGeometry %) results))]
197 (if (>= (count targets) 2)
198 (sort-by
199 #(let [v
200 (jme-to-blender
201 (.mult
202 (.inverse (.getWorldRotation joint))
203 (.subtract (.getWorldTranslation %)
204 (.getWorldTranslation joint))))]
205 (println-repl (.getName %) ":" v)
206 (.dot (Vector3f. 1 1 1)
207 v))
208 (take 2 targets))
209 (recur (float (* radius 2))))))))
211 (defn world-to-local
212 "Convert the world coordinates into coordinates relative to the
213 object (i.e. local coordinates), taking into account the rotation
214 of object."
215 [#^Spatial object world-coordinate]
216 (let [out (Vector3f.)]
217 (.worldToLocal object world-coordinate out) out))
219 (defn local-to-world
220 "Convert the local coordinates into coordinates into world relative
221 coordinates"
222 [#^Spatial object local-coordinate]
223 (let [world-coordinate (Vector3f.)]
224 (.localToWorld object local-coordinate world-coordinate)
225 world-coordinate))
227 (defmulti joint-dispatch
228 "Translate blender pseudo-joints into real JME joints."
229 (fn [constraints & _]
230 (:type constraints)))
232 (defmethod joint-dispatch :point
233 [constraints control-a control-b pivot-a pivot-b rotation]
234 (println-repl "creating POINT2POINT joint")
235 (Point2PointJoint.
236 control-a
237 control-b
238 pivot-a
239 pivot-b))
241 (defmethod joint-dispatch :hinge
242 [constraints control-a control-b pivot-a pivot-b rotation]
243 (println-repl "creating HINGE joint")
244 (let [axis
245 (if-let
246 [axis (:axis constraints)]
247 axis
248 Vector3f/UNIT_X)
249 [limit-1 limit-2] (:limit constraints)
250 hinge-axis
251 (.mult
252 rotation
253 (blender-to-jme axis))]
254 (doto
255 (HingeJoint.
256 control-a
257 control-b
258 pivot-a
259 pivot-b
260 hinge-axis
261 hinge-axis)
262 (.setLimit limit-1 limit-2))))
264 (defmethod joint-dispatch :cone
265 [constraints control-a control-b pivot-a pivot-b rotation]
266 (let [limit-xz (:limit-xz constraints)
267 limit-xy (:limit-xy constraints)
268 twist (:twist constraints)]
270 (println-repl "creating CONE joint")
271 (println-repl rotation)
272 (println-repl
273 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))
274 (println-repl
275 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))
276 (println-repl
277 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))
278 (doto
279 (ConeJoint.
280 control-a
281 control-b
282 pivot-a
283 pivot-b
284 rotation
285 rotation)
286 (.setLimit (float limit-xz)
287 (float limit-xy)
288 (float twist)))))
290 (defn connect
291 "here are some examples:
292 {:type :point}
293 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}
294 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)
296 {:type :cone :limit-xz 0]
297 :limit-xy 0]
298 :twist 0]} (use XZY rotation mode in blender!)"
299 [#^Node obj-a #^Node obj-b #^Node joint]
300 (let [control-a (.getControl obj-a RigidBodyControl)
301 control-b (.getControl obj-b RigidBodyControl)
302 joint-center (.getWorldTranslation joint)
303 joint-rotation (.toRotationMatrix (.getWorldRotation joint))
304 pivot-a (world-to-local obj-a joint-center)
305 pivot-b (world-to-local obj-b joint-center)]
307 (if-let [constraints
308 (map-vals
309 eval
310 (read-string
311 (meta-data joint "joint")))]
312 ;; A side-effect of creating a joint registers
313 ;; it with both physics objects which in turn
314 ;; will register the joint with the physics system
315 ;; when the simulation is started.
316 (do
317 (println-repl "creating joint between"
318 (.getName obj-a) "and" (.getName obj-b))
319 (joint-dispatch constraints
320 control-a control-b
321 pivot-a pivot-b
322 joint-rotation))
323 (println-repl "could not find joint meta-data!"))))
325 (defn assemble-creature [#^Node pieces joints]
326 (dorun
327 (map
328 (fn [geom]
329 (let [physics-control
330 (RigidBodyControl.
331 (HullCollisionShape.
332 (.getMesh geom))
333 (if-let [mass (meta-data geom "mass")]
334 (do
335 (println-repl
336 "setting" (.getName geom) "mass to" (float mass))
337 (float mass))
338 (float 1)))]
340 (.addControl geom physics-control)))
341 (filter #(isa? (class %) Geometry )
342 (node-seq pieces))))
343 (dorun
344 (map
345 (fn [joint]
346 (let [[obj-a obj-b]
347 (joint-targets pieces joint)]
348 (connect obj-a obj-b joint)))
349 joints))
350 pieces)
352 (declare blender-creature)
354 (def hand "Models/creature1/one.blend")
356 (def worm "Models/creature1/try-again.blend")
358 (def touch "Models/creature1/touch.blend")
360 (defn worm-model [] (load-blender-model worm))
362 (defn x-ray [#^ColorRGBA color]
363 (doto (Material. (asset-manager)
364 "Common/MatDefs/Misc/Unshaded.j3md")
365 (.setColor "Color" color)
366 (-> (.getAdditionalRenderState)
367 (.setDepthTest false))))
369 (defn colorful []
370 (.getChild (worm-model) "worm-21"))
372 (import jme3tools.converters.ImageToAwt)
374 (import ij.ImagePlus)
376 ;; Every Mesh has many triangles, each with its own index.
377 ;; Every vertex has its own index as well.
379 (defn tactile-sensor-image
380 "Return the touch-sensor distribution image in BufferedImage format,
381 or nil if it does not exist."
382 [#^Geometry obj]
383 (if-let [image-path (meta-data obj "touch")]
384 (ImageToAwt/convert
385 (.getImage
386 (.loadTexture
387 (asset-manager)
388 image-path))
389 false false 0)))
391 (import ij.process.ImageProcessor)
392 (import java.awt.image.BufferedImage)
394 (def white -1)
396 (defn filter-pixels
397 "List the coordinates of all pixels matching pred, within the bounds
398 provided. Bounds -> [x0 y0 width height]"
399 {:author "Dylan Holmes"}
400 ([pred #^BufferedImage image]
401 (filter-pixels pred image [0 0 (.getWidth image) (.getHeight image)]))
402 ([pred #^BufferedImage image [x0 y0 width height]]
403 ((fn accumulate [x y matches]
404 (cond
405 (>= y (+ height y0)) matches
406 (>= x (+ width x0)) (recur 0 (inc y) matches)
407 (pred (.getRGB image x y))
408 (recur (inc x) y (conj matches [x y]))
409 :else (recur (inc x) y matches)))
410 x0 y0 [])))
412 (defn white-coordinates
413 "Coordinates of all the white pixels in a subset of the image."
414 ([#^BufferedImage image bounds]
415 (filter-pixels #(= % white) image bounds))
416 ([#^BufferedImage image]
417 (filter-pixels #(= % white) image)))
419 (defn triangle
420 "Get the triangle specified by triangle-index from the mesh within
421 bounds."
422 [#^Mesh mesh triangle-index]
423 (let [scratch (Triangle.)]
424 (.getTriangle mesh triangle-index scratch)
425 scratch))
427 (defn triangle-vertex-indices
428 "Get the triangle vertex indices of a given triangle from a given
429 mesh."
430 [#^Mesh mesh triangle-index]
431 (let [indices (int-array 3)]
432 (.getTriangle mesh triangle-index indices)
433 (vec indices)))
435 (defn vertex-UV-coord
436 "Get the uv-coordinates of the vertex named by vertex-index"
437 [#^Mesh mesh vertex-index]
438 (let [UV-buffer
439 (.getData
440 (.getBuffer
441 mesh
442 VertexBuffer$Type/TexCoord))]
443 [(.get UV-buffer (* vertex-index 2))
444 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
446 (defn triangle-UV-coord
447 "Get the uv-cooridnates of the triangle's verticies."
448 [#^Mesh mesh width height triangle-index]
449 (map (fn [[u v]] (vector (* width u) (* height v)))
450 (map (partial vertex-UV-coord mesh)
451 (triangle-vertex-indices mesh triangle-index))))
453 (defn same-side?
454 "Given the points p1 and p2 and the reference point ref, is point p
455 on the same side of the line that goes through p1 and p2 as ref is?"
456 [p1 p2 ref p]
457 (<=
458 0
459 (.dot
460 (.cross (.subtract p2 p1) (.subtract p p1))
461 (.cross (.subtract p2 p1) (.subtract ref p1)))))
463 (defn triangle-seq [#^Triangle tri]
464 [(.get1 tri) (.get2 tri) (.get3 tri)])
466 (defn vector3f-seq [#^Vector3f v]
467 [(.getX v) (.getY v) (.getZ v)])
469 (defn inside-triangle?
470 "Is the point inside the triangle?"
471 {:author "Dylan Holmes"}
472 [#^Triangle tri #^Vector3f p]
473 (let [[vert-1 vert-2 vert-3] (triangle-seq tri)]
474 (and
475 (same-side? vert-1 vert-2 vert-3 p)
476 (same-side? vert-2 vert-3 vert-1 p)
477 (same-side? vert-3 vert-1 vert-2 p))))
479 (defn triangle->matrix4f
480 "Converts the triangle into a 4x4 matrix: The first three columns
481 contain the vertices of the triangle; the last contains the unit
482 normal of the triangle. The bottom row is filled with 1s."
483 [#^Triangle t]
484 (let [mat (Matrix4f.)
485 [vert-1 vert-2 vert-3]
486 ((comp vec map) #(.get t %) (range 3))
487 unit-normal (do (.calculateNormal t)(.getNormal t))
488 vertices [vert-1 vert-2 vert-3 unit-normal]]
489 (dorun
490 (for [row (range 4) col (range 3)]
491 (do
492 (.set mat col row (.get (vertices row)col))
493 (.set mat 3 row 1))))
494 mat))
496 (defn triangle-transformation
497 "Returns the affine transformation that converts each vertex in the
498 first triangle into the corresponding vertex in the second
499 triangle."
500 [#^Triangle tri-1 #^Triangle tri-2]
501 (.mult
502 (triangle->matrix4f tri-2)
503 (.invert (triangle->matrix4f tri-1))))
505 (defn point->vector2f [[u v]]
506 (Vector2f. u v))
508 (defn vector2f->vector3f [v]
509 (Vector3f. (.getX v) (.getY v) 0))
511 (defn map-triangle [f #^Triangle tri]
512 (Triangle.
513 (f 0 (.get1 tri))
514 (f 1 (.get2 tri))
515 (f 2 (.get3 tri))))
517 (defn points->triangle
518 "Convert a list of points into a triangle."
519 [points]
520 (apply #(Triangle. %1 %2 %3)
521 (map (fn [point]
522 (let [point (vec point)]
523 (Vector3f. (get point 0 0)
524 (get point 1 0)
525 (get point 2 0))))
526 (take 3 points))))
528 (defn convex-bounds
529 "Dimensions of the smallest integer bounding square of the list of
530 2D verticies in the form: [x y width height]."
531 [uv-verts]
532 (let [xs (map first uv-verts)
533 ys (map second uv-verts)
534 x0 (Math/floor (apply min xs))
535 y0 (Math/floor (apply min ys))
536 x1 (Math/ceil (apply max xs))
537 y1 (Math/ceil (apply max ys))]
538 [x0 y0 (- x1 x0) (- y1 y0)]))
540 (defn sensors-in-triangle
541 "Find the locations of the touch sensors within a triangle in both
542 UV and gemoetry relative coordinates."
543 [image mesh tri-index]
544 (let [width (.getWidth image)
545 height (.getHeight image)
546 UV-vertex-coords (triangle-UV-coord mesh width height tri-index)
547 bounds (convex-bounds UV-vertex-coords)
549 cutout-triangle (points->triangle UV-vertex-coords)
550 UV-sensor-coords
551 (filter (comp (partial inside-triangle? cutout-triangle)
552 (fn [[u v]] (Vector3f. u v 0)))
553 (white-coordinates image bounds))
554 UV->geometry (triangle-transformation
555 cutout-triangle
556 (triangle mesh tri-index))
557 geometry-sensor-coords
558 (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0)))
559 UV-sensor-coords)]
560 {:UV UV-sensor-coords :geometry geometry-sensor-coords}))
562 (defn-memo locate-feelers
563 "Search the geometry's tactile UV image for touch sensors, returning
564 their positions in geometry-relative coordinates."
565 [#^Geometry geo]
566 (let [mesh (.getMesh geo)
567 num-triangles (.getTriangleCount mesh)]
568 (if-let [image (tactile-sensor-image geo)]
569 (map
570 (partial sensors-in-triangle image mesh)
571 (range num-triangles))
572 (repeat (.getTriangleCount mesh) {:UV nil :geometry nil}))))
574 (use 'clojure.contrib.def)
576 (defn-memo touch-topology [#^Gemoetry geo]
577 (vec (collapse (reduce concat (map :UV (locate-feelers geo))))))
579 (defn-memo feeler-coordinates [#^Geometry geo]
580 (vec (map :geometry (locate-feelers geo))))
582 (defn enable-touch [#^Geometry geo]
583 (let [feeler-coords (feeler-coordinates geo)
584 tris (triangles geo)
585 limit 0.1
586 ;;results (CollisionResults.)
587 ]
588 (if (empty? (touch-topology geo))
589 nil
590 (fn [node]
591 (let [sensor-origins
592 (map
593 #(map (partial local-to-world geo) %)
594 feeler-coords)
595 triangle-normals
596 (map (partial get-ray-direction geo)
597 tris)
598 rays
599 (flatten
600 (map (fn [origins norm]
601 (map #(doto (Ray. % norm)
602 (.setLimit limit)) origins))
603 sensor-origins triangle-normals))]
604 (vector
605 (touch-topology geo)
606 (vec
607 (for [ray rays]
608 (do
609 (let [results (CollisionResults.)]
610 (.collideWith node ray results)
611 (let [touch-objects
612 (set
613 (filter #(not (= geo %))
614 (map #(.getGeometry %) results)))]
615 (if (> (count touch-objects) 0)
616 1 0))))))))))))
618 (defn touch [#^Node pieces]
619 (filter (comp not nil?)
620 (map enable-touch
621 (filter #(isa? (class %) Geometry)
622 (node-seq pieces)))))
625 ;; human eye transmits 62kb/s to brain Bandwidth is 8.75 Mb/s
626 ;; http://en.wikipedia.org/wiki/Retina
628 (defn test-eye []
629 (.getChild
630 (.getChild (worm-model) "eyes")
631 "eye"))
634 (defn retina-sensor-image
635 "Return a map of pixel selection functions to BufferedImages
636 describing the distribution of light-sensitive components on this
637 geometry's surface. Each function creates an integer from the rgb
638 values found in the pixel. :red, :green, :blue, :gray are already
639 defined as extracting the red green blue and average components
640 respectively."
641 [#^Spatial eye]
642 (if-let [eye-map (meta-data eye "eye")]
643 (map-vals
644 #(ImageToAwt/convert
645 (.getImage (.loadTexture (asset-manager) %))
646 false false 0)
647 (eval (read-string eye-map)))))
649 (defn eye-dimensions
650 "returns the width and height specified in the metadata of the eye"
651 [#^Spatial eye]
652 (let [dimensions
653 (map #(vector (.getWidth %) (.getHeight %))
654 (vals (retina-sensor-image eye)))]
655 [(apply max (map first dimensions))
656 (apply max (map second dimensions))]))
658 (defn creature-eyes
659 "The eye nodes which are children of the \"eyes\" node in the
660 creature."
661 [#^Node creature]
662 (if-let [eye-node (.getChild creature "eyes")]
663 (seq (.getChildren eye-node))
664 (do (println-repl "could not find eyes node") [])))
666 ;; Here's how vision will work.
668 ;; Make the continuation in scene-processor take FrameBuffer,
669 ;; byte-buffer, BufferedImage already sized to the correct
670 ;; dimensions. the continuation will decide wether to "mix" them
671 ;; into the BufferedImage, lazily ignore them, or mix them halfway
672 ;; and call c/graphics card routines.
674 ;; (vision creature) will take an optional :skip argument which will
675 ;; inform the continuations in scene processor to skip the given
676 ;; number of cycles; 0 means that no cycles will be skipped.
678 ;; (vision creature) will return [init-functions sensor-functions].
679 ;; The init-functions are each single-arg functions that take the
680 ;; world and register the cameras and must each be called before the
681 ;; corresponding sensor-functions. Each init-function returns the
682 ;; viewport for that eye which can be manipulated, saved, etc. Each
683 ;; sensor-function is a thunk and will return data in the same
684 ;; format as the tactile-sensor functions; the structure is
685 ;; [topology, sensor-data]. Internally, these sensor-functions
686 ;; maintain a reference to sensor-data which is periodically updated
687 ;; by the continuation function established by its init-function.
688 ;; They can be queried every cycle, but their information may not
689 ;; necessairly be different every cycle.
691 ;; Each eye in the creature in blender will work the same way as
692 ;; joints -- a zero dimensional object with no geometry whose local
693 ;; coordinate system determines the orientation of the resulting
694 ;; eye. All eyes will have a parent named "eyes" just as all joints
695 ;; have a parent named "joints". The resulting camera will be a
696 ;; ChaseCamera or a CameraNode bound to the geo that is closest to
697 ;; the eye marker. The eye marker will contain the metadata for the
698 ;; eye, and will be moved by it's bound geometry. The dimensions of
699 ;; the eye's camera are equal to the dimensions of the eye's "UV"
700 ;; map.
703 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
705 ;; Ears work the same way as vision.
707 ;; (hearing creature) will return [init-functions
708 ;; sensor-functions]. The init functions each take the world and
709 ;; register a SoundProcessor that does foureier transforms on the
710 ;; incommong sound data, making it available to each sensor function.
712 (defn creature-ears
713 "The ear nodes which are children of the \"ears\" node in the
714 creature."
715 [#^Node creature]
716 (if-let [ear-node (.getChild creature "ears")]
717 (seq (.getChildren ear-node))
718 (do (println-repl "could not find ears node") [])))
720 (defn closest-node
721 "The closest object in creature to the given node."
722 [#^Node creature #^Node eye]
723 (loop [radius (float 0.01)]
724 (let [results (CollisionResults.)]
725 (.collideWith
726 creature
727 (BoundingBox. (.getWorldTranslation eye)
728 radius radius radius)
729 results)
730 (if-let [target (first results)]
731 (.getGeometry target)
732 (recur (float (* 2 radius)))))))
734 (defn bind-sense
735 "Bind the sense to the Spatial such that it will maintain its
736 current position relative to the Spatial no matter how the spatial
737 moves. 'sense can be either a Camera or Listener object."
738 [#^Spatial obj sense]
739 (let [sense-offset (.subtract (.getLocation sense)
740 (.getWorldTranslation obj))
741 initial-sense-rotation (Quaternion. (.getRotation sense))
742 base-anti-rotation (.inverse (.getWorldRotation obj))]
743 (.addControl
744 obj
745 (proxy [AbstractControl] []
746 (controlUpdate [tpf]
747 (let [total-rotation
748 (.mult base-anti-rotation (.getWorldRotation obj))]
749 (.setLocation sense
750 (.add
751 (.mult total-rotation sense-offset)
752 (.getWorldTranslation obj)))
753 (.setRotation sense
754 (.mult total-rotation initial-sense-rotation))))
755 (controlRender [_ _])))))
758 (defn update-listener-velocity
759 "Update the listener's velocity every update loop."
760 [#^Spatial obj #^Listener lis]
761 (let [old-position (atom (.getLocation lis))]
762 (.addControl
763 obj
764 (proxy [AbstractControl] []
765 (controlUpdate [tpf]
766 (let [new-position (.getLocation lis)]
767 (.setVelocity
768 lis
769 (.mult (.subtract new-position @old-position)
770 (float (/ tpf))))
771 (reset! old-position new-position)))
772 (controlRender [_ _])))))
774 (import com.aurellem.capture.audio.AudioSendRenderer)
776 (defn attach-ear
777 [#^Application world #^Node creature #^Spatial ear continuation]
778 (let [target (closest-node creature ear)
779 lis (Listener.)
780 audio-renderer (.getAudioRenderer world)
781 sp (sound-processor continuation)]
782 (println-repl "audio-renderer is " audio-renderer)
783 (.setLocation lis (.getWorldTranslation ear))
784 (.setRotation lis (.getWorldRotation ear))
785 (bind-sense target lis)
786 (update-listener-velocity target lis)
787 (.addListener audio-renderer lis)
788 (.registerSoundProcessor audio-renderer lis sp)))
790 (defn enable-hearing
791 [#^Node creature #^Spatial ear]
792 (let [hearing-data (atom [])]
793 [(fn [world]
794 (attach-ear world creature ear
795 (fn [data]
796 (reset! hearing-data (vec data)))))
797 [(fn []
798 (let [data @hearing-data
799 topology
800 (vec (map #(vector % 0) (range 0 (count data))))
801 scaled-data
802 (vec
803 (map
804 #(rem (int (* 255 (/ (+ 1 %) 2))) 256)
805 data))]
806 (println-repl (take 10 scaled-data))
807 [topology scaled-data]))
808 ]]))
810 (defn hearing
811 [#^Node creature]
812 (reduce
813 (fn [[init-a senses-a]
814 [init-b senses-b]]
815 [(conj init-a init-b)
816 (into senses-a senses-b)])
817 [[][]]
818 (for [ear (creature-ears creature)]
819 (enable-hearing creature ear))))
821 (defn attach-eye
822 "Attach a Camera to the appropiate area and return the Camera."
823 [#^Node creature #^Spatial eye]
824 (let [target (closest-node creature eye)
825 [cam-width cam-height] (eye-dimensions eye)
826 cam (Camera. cam-width cam-height)]
827 (.setLocation cam (.getWorldTranslation eye))
828 (.setRotation cam (.getWorldRotation eye))
829 (.setFrustumPerspective
830 cam 45 (/ (.getWidth cam) (.getHeight cam))
831 1 1000)
832 (bind-sense target cam)
833 cam))
835 (def presets
836 {:all 0xFFFFFF
837 :red 0xFF0000
838 :blue 0x0000FF
839 :green 0x00FF00})
841 (defn enable-vision
842 "return [init-function sensor-functions] for a particular eye"
843 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
844 (let [retinal-map (retina-sensor-image eye)
845 camera (attach-eye creature eye)
846 vision-image
847 (atom
848 (BufferedImage. (.getWidth camera)
849 (.getHeight camera)
850 BufferedImage/TYPE_BYTE_BINARY))]
851 [(fn [world]
852 (add-eye
853 world camera
854 (let [counter (atom 0)]
855 (fn [r fb bb bi]
856 (if (zero? (rem (swap! counter inc) (inc skip)))
857 (reset! vision-image (BufferedImage! r fb bb bi)))))))
858 (vec
859 (map
860 (fn [[key image]]
861 (let [whites (white-coordinates image)
862 topology (vec (collapse whites))
863 mask (presets key)]
864 (fn []
865 (vector
866 topology
867 (vec
868 (for [[x y] whites]
869 (bit-and
870 mask (.getRGB @vision-image x y))))))))
871 retinal-map))]))
873 (defn vision
874 [#^Node creature & {skip :skip :or {skip 0}}]
875 (reduce
876 (fn [[init-a senses-a]
877 [init-b senses-b]]
878 [(conj init-a init-b)
879 (into senses-a senses-b)])
880 [[][]]
881 (for [eye (creature-eyes creature)]
882 (enable-vision creature eye))))
885 (defn blender-creature
886 "Return a creature with all joints in place."
887 [blender-path]
888 (let [model (load-blender-model blender-path)
889 joints
890 (if-let [joint-node (.getChild model "joints")]
891 (seq (.getChildren joint-node))
892 (do (println-repl "could not find joints node") []))]
893 (assemble-creature model joints)))
895 (defn debug-window
896 "creates function that offers a debug view of sensor data"
897 []
898 (let [vi (view-image)]
899 (fn
900 [[coords sensor-data]]
901 (let [image (points->image coords)]
902 (dorun
903 (for [i (range (count coords))]
904 (.setRGB image ((coords i) 0) ((coords i) 1)
905 ({0 0x000000
906 1 0xFFFFFF} (sensor-data i)))))
907 (vi image)))))
909 (defn debug-vision-window
910 "creates function that offers a debug view of sensor data"
911 []
912 (let [vi (view-image)]
913 (fn
914 [[coords sensor-data]]
915 (let [image (points->image coords)]
916 (dorun
917 (for [i (range (count coords))]
918 (.setRGB image ((coords i) 0) ((coords i) 1)
919 (sensor-data i))))
920 (vi image)))))
922 (defn debug-hearing-window
923 "view audio data"
924 [height]
925 (let [vi (view-image)]
926 (fn [[coords sensor-data]]
927 (let [image (BufferedImage. (count coords) height
928 BufferedImage/TYPE_INT_RGB)]
929 (dorun
930 (for [x (range (count coords))]
931 (dorun
932 (for [y (range height)]
933 (let [raw-sensor (sensor-data x)]
934 (.setRGB image x y
935 (+ raw-sensor
936 (bit-shift-left raw-sensor 8)
937 (bit-shift-left raw-sensor 16))))))))
938 (vi image)))))
942 ;;(defn test-touch [world creature]
949 (defn test-creature [thing]
950 (let [x-axis
951 (box 1 0.01 0.01 :physical? false :color ColorRGBA/Red)
952 y-axis
953 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green)
954 z-axis
955 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue)
956 creature (blender-creature thing)
957 touch-nerves (touch creature)
958 touch-debug-windows (map (fn [_] (debug-window)) touch-nerves)
959 [init-vision-fns vision-data] (vision creature)
960 vision-debug (map (fn [_] (debug-vision-window)) vision-data)
961 me (sphere 0.5 :color ColorRGBA/Blue :physical? false)
962 [init-hearing-fns hearing-senses] (hearing creature)
963 hearing-windows (map (fn [_] (debug-hearing-window 50))
964 hearing-senses)
965 bang (AudioNode. (asset-manager)
966 "Sounds/dream.wav" false)
967 ;; dream
969 ]
970 (world
971 (nodify [creature
972 (box 10 2 10 :position (Vector3f. 0 -9 0)
973 :color ColorRGBA/Gray :mass 0)
974 x-axis y-axis z-axis
975 me
976 ])
977 (merge standard-debug-controls
978 {"key-return"
979 (fn [_ value]
980 (if value
981 (do
982 (println-repl "play-sound")
983 (.play bang))))})
984 (fn [world]
985 (light-up-everything world)
986 (enable-debug world)
987 (dorun (map #(% world) init-vision-fns))
988 (dorun (map #(% world) init-hearing-fns))
990 (add-eye world
991 (attach-eye creature (test-eye))
992 (comp (view-image) BufferedImage!))
994 (add-eye world (.getCamera world) no-op)
996 ;;(com.aurellem.capture.Capture/captureVideo
997 ;; world (file-str "/home/r/proj/ai-videos/hand"))
998 ;;(.setTimer world (RatchetTimer. 60))
999 (speed-up world)
1000 ;;(set-gravity world (Vector3f. 0 0 0))
1002 (fn [world tpf]
1003 ;;(dorun
1004 ;; (map #(%1 %2) touch-nerves (repeat (.getRootNode world))))
1008 (dorun
1009 (map #(%1 (%2 (.getRootNode world)))
1010 touch-debug-windows touch-nerves))
1012 (dorun
1013 (map #(%1 (%2))
1014 vision-debug vision-data))
1015 (dorun
1016 (map #(%1 (%2)) hearing-windows hearing-senses))
1019 ;;(println-repl (vision-data))
1020 (.setLocalTranslation me (.getLocation (.getCamera world)))
1024 ;;(let [timer (atom 0)]
1025 ;; (fn [_ _]
1026 ;; (swap! timer inc)
1027 ;; (if (= (rem @timer 60) 0)
1028 ;; (println-repl (float (/ @timer 60))))))
1029 )))
1039 ;;; experiments in collisions
1043 (defn collision-test []
1044 (let [b-radius 1
1045 b-position (Vector3f. 0 0 0)
1046 obj-b (box 1 1 1 :color ColorRGBA/Blue
1047 :position b-position
1048 :mass 0)
1049 node (nodify [obj-b])
1050 bounds-b
1051 (doto (Picture.)
1052 (.setHeight 50)
1053 (.setWidth 50)
1054 (.setImage (asset-manager)
1055 "Models/creature1/hand.png"
1056 false
1057 ))
1059 ;;(Ray. (Vector3f. 0 -5 0) (.normalize (Vector3f. 0 1 0)))
1061 collisions
1062 (let [cr (CollisionResults.)]
1063 (.collideWith node bounds-b cr)
1064 (println (map #(.getContactPoint %) cr))
1065 cr)
1067 ;;collision-points
1068 ;;(map #(sphere 0.1 :position (.getContactPoint %))
1069 ;; collisions)
1071 ;;node (nodify (conj collision-points obj-b))
1073 sim
1074 (world node
1075 {"key-space"
1076 (fn [_ value]
1077 (if value
1078 (let [cr (CollisionResults.)]
1079 (.collideWith node bounds-b cr)
1080 (println-repl (map #(.getContactPoint %) cr))
1081 cr)))}
1082 no-op
1083 no-op)
1086 sim
1088 ))
1091 ;; the camera will stay in its initial position/rotation with relation
1092 ;; to the spatial.
1095 (defn follow-test
1096 "show a camera that stays in the same relative position to a blue cube."
1097 []
1098 (let [camera-pos (Vector3f. 0 30 0)
1099 rock (box 1 1 1 :color ColorRGBA/Blue
1100 :position (Vector3f. 0 10 0)
1101 :mass 30
1103 rot (.getWorldRotation rock)
1105 table (box 3 1 10 :color ColorRGBA/Gray :mass 0
1106 :position (Vector3f. 0 -3 0))]
1108 (world
1109 (nodify [rock table])
1110 standard-debug-controls
1111 (fn [world]
1112 (let
1113 [cam (doto (.clone (.getCamera world))
1114 (.setLocation camera-pos)
1115 (.lookAt Vector3f/ZERO
1116 Vector3f/UNIT_X))]
1117 (bind-sense rock cam)
1119 (.setTimer world (RatchetTimer. 60))
1120 (add-eye world cam (comp (view-image) BufferedImage!))
1121 (add-eye world (.getCamera world) no-op))
1123 (fn [_ _] (println-repl rot)))))
1127 #+end_src
1129 #+results: body-1
1130 : #'cortex.silly/test-creature
1133 * COMMENT purgatory
1134 #+begin_src clojure
1135 (defn bullet-trans []
1136 (let [obj-a (sphere 0.5 :color ColorRGBA/Red
1137 :position (Vector3f. -10 5 0))
1138 obj-b (sphere 0.5 :color ColorRGBA/Blue
1139 :position (Vector3f. -10 -5 0)
1140 :mass 0)
1141 control-a (.getControl obj-a RigidBodyControl)
1142 control-b (.getControl obj-b RigidBodyControl)
1143 swivel
1144 (.toRotationMatrix
1145 (doto (Quaternion.)
1146 (.fromAngleAxis (/ Math/PI 2)
1147 Vector3f/UNIT_X)))]
1148 (doto
1149 (ConeJoint.
1150 control-a control-b
1151 (Vector3f. 0 5 0)
1152 (Vector3f. 0 -5 0)
1153 swivel swivel)
1154 (.setLimit (* 0.6 (/ Math/PI 4))
1155 (/ Math/PI 4)
1156 (* Math/PI 0.8)))
1157 (world (nodify
1158 [obj-a obj-b])
1159 standard-debug-controls
1160 enable-debug
1161 no-op)))
1164 (defn bullet-trans* []
1165 (let [obj-a (box 1.5 0.5 0.5 :color ColorRGBA/Red
1166 :position (Vector3f. 5 0 0)
1167 :mass 90)
1168 obj-b (sphere 0.5 :color ColorRGBA/Blue
1169 :position (Vector3f. -5 0 0)
1170 :mass 0)
1171 control-a (.getControl obj-a RigidBodyControl)
1172 control-b (.getControl obj-b RigidBodyControl)
1173 move-up? (atom nil)
1174 move-down? (atom nil)
1175 move-left? (atom nil)
1176 move-right? (atom nil)
1177 roll-left? (atom nil)
1178 roll-right? (atom nil)
1179 force 100
1180 swivel
1181 (.toRotationMatrix
1182 (doto (Quaternion.)
1183 (.fromAngleAxis (/ Math/PI 2)
1184 Vector3f/UNIT_X)))
1185 x-move
1186 (doto (Matrix3f.)
1187 (.fromStartEndVectors Vector3f/UNIT_X
1188 (.normalize (Vector3f. 1 1 0))))
1190 timer (atom 0)]
1191 (doto
1192 (ConeJoint.
1193 control-a control-b
1194 (Vector3f. -8 0 0)
1195 (Vector3f. 2 0 0)
1196 ;;swivel swivel
1197 ;;Matrix3f/IDENTITY Matrix3f/IDENTITY
1198 x-move Matrix3f/IDENTITY
1200 (.setCollisionBetweenLinkedBodys false)
1201 (.setLimit (* 1 (/ Math/PI 4)) ;; twist
1202 (* 1 (/ Math/PI 4)) ;; swing span in X-Y plane
1203 (* 0 (/ Math/PI 4)))) ;; swing span in Y-Z plane
1204 (world (nodify
1205 [obj-a obj-b])
1206 (merge standard-debug-controls
1207 {"key-r" (fn [_ pressed?] (reset! move-up? pressed?))
1208 "key-t" (fn [_ pressed?] (reset! move-down? pressed?))
1209 "key-f" (fn [_ pressed?] (reset! move-left? pressed?))
1210 "key-g" (fn [_ pressed?] (reset! move-right? pressed?))
1211 "key-v" (fn [_ pressed?] (reset! roll-left? pressed?))
1212 "key-b" (fn [_ pressed?] (reset! roll-right? pressed?))})
1214 (fn [world]
1215 (enable-debug world)
1216 (set-gravity world Vector3f/ZERO)
1219 (fn [world _]
1221 (if @move-up?
1222 (.applyForce control-a
1223 (Vector3f. force 0 0)
1224 (Vector3f. 0 0 0)))
1225 (if @move-down?
1226 (.applyForce control-a
1227 (Vector3f. (- force) 0 0)
1228 (Vector3f. 0 0 0)))
1229 (if @move-left?
1230 (.applyForce control-a
1231 (Vector3f. 0 force 0)
1232 (Vector3f. 0 0 0)))
1233 (if @move-right?
1234 (.applyForce control-a
1235 (Vector3f. 0 (- force) 0)
1236 (Vector3f. 0 0 0)))
1238 (if @roll-left?
1239 (.applyForce control-a
1240 (Vector3f. 0 0 force)
1241 (Vector3f. 0 0 0)))
1242 (if @roll-right?
1243 (.applyForce control-a
1244 (Vector3f. 0 0 (- force))
1245 (Vector3f. 0 0 0)))
1247 (if (zero? (rem (swap! timer inc) 100))
1248 (.attachChild
1249 (.getRootNode world)
1250 (sphere 0.05 :color ColorRGBA/Yellow
1251 :physical? false :position
1252 (.getWorldTranslation obj-a)))))
1254 ))
1256 (defn transform-trianglesdsd
1257 "Transform that converts each vertex in the first triangle
1258 into the corresponding vertex in the second triangle."
1259 [#^Triangle tri-1 #^Triangle tri-2]
1260 (let [in [(.get1 tri-1)
1261 (.get2 tri-1)
1262 (.get3 tri-1)]
1263 out [(.get1 tri-2)
1264 (.get2 tri-2)
1265 (.get3 tri-2)]]
1266 (let [translate (doto (Matrix4f.) (.setTranslation (.negate (in 0))))
1267 in* [(.mult translate (in 0))
1268 (.mult translate (in 1))
1269 (.mult translate (in 2))]
1270 final-translation
1271 (doto (Matrix4f.)
1272 (.setTranslation (out 1)))
1274 rotate-1
1275 (doto (Matrix3f.)
1276 (.fromStartEndVectors
1277 (.normalize
1278 (.subtract
1279 (in* 1) (in* 0)))
1280 (.normalize
1281 (.subtract
1282 (out 1) (out 0)))))
1283 in** [(.mult rotate-1 (in* 0))
1284 (.mult rotate-1 (in* 1))
1285 (.mult rotate-1 (in* 2))]
1286 scale-factor-1
1287 (.mult
1288 (.normalize
1289 (.subtract
1290 (out 1)
1291 (out 0)))
1292 (/ (.length
1293 (.subtract (out 1)
1294 (out 0)))
1295 (.length
1296 (.subtract (in** 1)
1297 (in** 0)))))
1298 scale-1 (doto (Matrix4f.) (.setScale scale-factor-1))
1299 in*** [(.mult scale-1 (in** 0))
1300 (.mult scale-1 (in** 1))
1301 (.mult scale-1 (in** 2))]
1309 (dorun (map println in))
1310 (println)
1311 (dorun (map println in*))
1312 (println)
1313 (dorun (map println in**))
1314 (println)
1315 (dorun (map println in***))
1316 (println)
1318 ))))
1321 (defn world-setup [joint]
1322 (let [joint-position (Vector3f. 0 0 0)
1323 joint-rotation
1324 (.toRotationMatrix
1325 (.mult
1326 (doto (Quaternion.)
1327 (.fromAngleAxis
1328 (* 1 (/ Math/PI 4))
1329 (Vector3f. -1 0 0)))
1330 (doto (Quaternion.)
1331 (.fromAngleAxis
1332 (* 1 (/ Math/PI 2))
1333 (Vector3f. 0 0 1)))))
1334 top-position (.mult joint-rotation (Vector3f. 8 0 0))
1336 origin (doto
1337 (sphere 0.1 :physical? false :color ColorRGBA/Cyan
1338 :position top-position))
1339 top (doto
1340 (sphere 0.1 :physical? false :color ColorRGBA/Yellow
1341 :position top-position)
1343 (.addControl
1344 (RigidBodyControl.
1345 (CapsuleCollisionShape. 0.5 1.5 1) (float 20))))
1346 bottom (doto
1347 (sphere 0.1 :physical? false :color ColorRGBA/DarkGray
1348 :position (Vector3f. 0 0 0))
1349 (.addControl
1350 (RigidBodyControl.
1351 (CapsuleCollisionShape. 0.5 1.5 1) (float 0))))
1352 table (box 10 2 10 :position (Vector3f. 0 -20 0)
1353 :color ColorRGBA/Gray :mass 0)
1354 a (.getControl top RigidBodyControl)
1355 b (.getControl bottom RigidBodyControl)]
1357 (cond
1358 (= joint :cone)
1360 (doto (ConeJoint.
1361 a b
1362 (world-to-local top joint-position)
1363 (world-to-local bottom joint-position)
1364 joint-rotation
1365 joint-rotation
1369 (.setLimit (* (/ 10) Math/PI)
1370 (* (/ 4) Math/PI)
1371 0)))
1372 [origin top bottom table]))
1374 (defn test-joint [joint]
1375 (let [[origin top bottom floor] (world-setup joint)
1376 control (.getControl top RigidBodyControl)
1377 move-up? (atom false)
1378 move-down? (atom false)
1379 move-left? (atom false)
1380 move-right? (atom false)
1381 roll-left? (atom false)
1382 roll-right? (atom false)
1383 timer (atom 0)]
1385 (world
1386 (nodify [top bottom floor origin])
1387 (merge standard-debug-controls
1388 {"key-r" (fn [_ pressed?] (reset! move-up? pressed?))
1389 "key-t" (fn [_ pressed?] (reset! move-down? pressed?))
1390 "key-f" (fn [_ pressed?] (reset! move-left? pressed?))
1391 "key-g" (fn [_ pressed?] (reset! move-right? pressed?))
1392 "key-v" (fn [_ pressed?] (reset! roll-left? pressed?))
1393 "key-b" (fn [_ pressed?] (reset! roll-right? pressed?))})
1395 (fn [world]
1396 (light-up-everything world)
1397 (enable-debug world)
1398 (set-gravity world (Vector3f. 0 0 0))
1401 (fn [world _]
1402 (if (zero? (rem (swap! timer inc) 100))
1403 (do
1404 ;; (println-repl @timer)
1405 (.attachChild (.getRootNode world)
1406 (sphere 0.05 :color ColorRGBA/Yellow
1407 :position (.getWorldTranslation top)
1408 :physical? false))
1409 (.attachChild (.getRootNode world)
1410 (sphere 0.05 :color ColorRGBA/LightGray
1411 :position (.getWorldTranslation bottom)
1412 :physical? false))))
1414 (if @move-up?
1415 (.applyTorque control
1416 (.mult (.getPhysicsRotation control)
1417 (Vector3f. 0 0 10))))
1418 (if @move-down?
1419 (.applyTorque control
1420 (.mult (.getPhysicsRotation control)
1421 (Vector3f. 0 0 -10))))
1422 (if @move-left?
1423 (.applyTorque control
1424 (.mult (.getPhysicsRotation control)
1425 (Vector3f. 0 10 0))))
1426 (if @move-right?
1427 (.applyTorque control
1428 (.mult (.getPhysicsRotation control)
1429 (Vector3f. 0 -10 0))))
1430 (if @roll-left?
1431 (.applyTorque control
1432 (.mult (.getPhysicsRotation control)
1433 (Vector3f. -1 0 0))))
1434 (if @roll-right?
1435 (.applyTorque control
1436 (.mult (.getPhysicsRotation control)
1437 (Vector3f. 1 0 0))))))))
1441 (defprotocol Frame
1442 (frame [this]))
1444 (extend-type BufferedImage
1445 Frame
1446 (frame [image]
1447 (merge
1448 (apply
1449 hash-map
1450 (interleave
1451 (doall (for [x (range (.getWidth image)) y (range (.getHeight image))]
1452 (vector x y)))
1453 (doall (for [x (range (.getWidth image)) y (range (.getHeight image))]
1454 (let [data (.getRGB image x y)]
1455 (hash-map :r (bit-shift-right (bit-and 0xff0000 data) 16)
1456 :g (bit-shift-right (bit-and 0x00ff00 data) 8)
1457 :b (bit-and 0x0000ff data)))))))
1458 {:width (.getWidth image) :height (.getHeight image)})))
1461 (extend-type ImagePlus
1462 Frame
1463 (frame [image+]
1464 (frame (.getBufferedImage image+))))
1467 #+end_src
1470 * COMMENT generate source
1471 #+begin_src clojure :tangle ../src/cortex/silly.clj
1472 <<body-1>>
1473 #+end_src