view org/test-creature.org @ 124:90154bd674e9

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