view org/vision.org @ 262:0e85237d27a7

Added a diagram explaining RenderManager
author Dylan Holmes <ocsenave@gmail.com>
date Mon, 13 Feb 2012 06:48:40 -0600
parents 02b2e6f3fb43
children f8227f6d4ac6
line wrap: on
line source
1 #+title: Simulated Sense of Sight
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description: Simulated sight for AI research using JMonkeyEngine3 and clojure
5 #+keywords: computer vision, jMonkeyEngine3, clojure
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
8 #+babel: :mkdirp yes :noweb yes :exports both
10 * Vision
12 Vision is one of the most important senses for humans, so I need to
13 build a simulated sense of vision for my AI. I will do this with
14 simulated eyes. Each eye can be independely moved and should see its
15 own version of the world depending on where it is.
17 Making these simulated eyes a reality is simple bacause jMonkeyEngine
18 already conatains extensive support for multiple views of the same 3D
19 simulated world. The reason jMonkeyEngine has this support is because
20 the support is necessary to create games with split-screen
21 views. Multiple views are also used to create efficient
22 pseudo-reflections by rendering the scene from a certain perspective
23 and then projecting it back onto a surface in the 3D world.
25 #+caption: jMonkeyEngine supports multiple views to enable split-screen games, like GoldenEye, which was one of the first games to use split-screen views.
26 [[../images/goldeneye-4-player.png]]
28 * Brief Description of jMonkeyEngine's Rendering Pipeline
30 jMonkeyEngine allows you to create a =ViewPort=, which represents a
31 view of the simulated world. You can create as many of these as you
32 want. Every frame, the =RenderManager= iterates through each
33 =ViewPort=, rendering the scene in the GPU. For each =ViewPort= there
34 is a =FrameBuffer= which represents the rendered image in the GPU.
36 #+caption: =ViewPorts= are cameras in the world. During each frame, the =Rendermanager= records a snapshot of what each view is currently seeing.
37 #+attr_html:width="400"
38 [[../images/diagram_rendermanager.png]]
40 Each =ViewPort= can have any number of attached =SceneProcessor=
41 objects, which are called every time a new frame is rendered. A
42 =SceneProcessor= recieves its =ViewPort's= =FrameBuffer= and can do
43 whatever it wants to the data. Often this consists of invoking GPU
44 specific operations on the rendered image. The =SceneProcessor= can
45 also copy the GPU image data to RAM and process it with the CPU.
47 * The Vision Pipeline
49 Each eye in the simulated creature needs it's own =ViewPort= so that
50 it can see the world from its own perspective. To this =ViewPort=, I
51 add a =SceneProcessor= that feeds the visual data to any arbitray
52 continuation function for further processing. That continuation
53 function may perform both CPU and GPU operations on the data. To make
54 this easy for the continuation function, the =SceneProcessor=
55 maintains appropriatly sized buffers in RAM to hold the data. It does
56 not do any copying from the GPU to the CPU itself because it is a slow
57 operation.
59 #+name: pipeline-1
60 #+begin_src clojure
61 (defn vision-pipeline
62 "Create a SceneProcessor object which wraps a vision processing
63 continuation function. The continuation is a function that takes
64 [#^Renderer r #^FrameBuffer fb #^ByteBuffer b #^BufferedImage bi],
65 each of which has already been appropiately sized."
66 [continuation]
67 (let [byte-buffer (atom nil)
68 renderer (atom nil)
69 image (atom nil)]
70 (proxy [SceneProcessor] []
71 (initialize
72 [renderManager viewPort]
73 (let [cam (.getCamera viewPort)
74 width (.getWidth cam)
75 height (.getHeight cam)]
76 (reset! renderer (.getRenderer renderManager))
77 (reset! byte-buffer
78 (BufferUtils/createByteBuffer
79 (* width height 4)))
80 (reset! image (BufferedImage.
81 width height
82 BufferedImage/TYPE_4BYTE_ABGR))))
83 (isInitialized [] (not (nil? @byte-buffer)))
84 (reshape [_ _ _])
85 (preFrame [_])
86 (postQueue [_])
87 (postFrame
88 [#^FrameBuffer fb]
89 (.clear @byte-buffer)
90 (continuation @renderer fb @byte-buffer @image))
91 (cleanup []))))
92 #+end_src
94 The continuation function given to =(vision-pipeline)= above will be
95 given a =Renderer= and three containers for image data. The
96 =FrameBuffer= references the GPU image data, but the pixel data can
97 not be used directly on the CPU. The =ByteBuffer= and =BufferedImage=
98 are initially "empty" but are sized to hold the data in the
99 =FrameBuffer=. I call transfering the GPU image data to the CPU
100 structures "mixing" the image data. I have provided three functions to
101 do this mixing.
103 #+name: pipeline-2
104 #+begin_src clojure
105 (defn frameBuffer->byteBuffer!
106 "Transfer the data in the graphics card (Renderer, FrameBuffer) to
107 the CPU (ByteBuffer)."
108 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb]
109 (.readFrameBuffer r fb bb) bb)
111 (defn byteBuffer->bufferedImage!
112 "Convert the C-style BGRA image data in the ByteBuffer bb to the AWT
113 style ABGR image data and place it in BufferedImage bi."
114 [#^ByteBuffer bb #^BufferedImage bi]
115 (Screenshots/convertScreenShot bb bi) bi)
117 (defn BufferedImage!
118 "Continuation which will grab the buffered image from the materials
119 provided by (vision-pipeline)."
120 [#^Renderer r #^FrameBuffer fb #^ByteBuffer bb #^BufferedImage bi]
121 (byteBuffer->bufferedImage!
122 (frameBuffer->byteBuffer! r fb bb) bi))
123 #+end_src
125 Note that it is possible to write vision processing algorithms
126 entirely in terms of =BufferedImage= inputs. Just compose that
127 =BufferedImage= algorithm with =(BufferedImage!)=. However, a vision
128 processing algorithm that is entirely hosted on the GPU does not have
129 to pay for this convienence.
131 * COMMENT asdasd
133 (vision creature) will take an optional :skip argument which will
134 inform the continuations in scene processor to skip the given
135 number of cycles 0 means that no cycles will be skipped.
137 (vision creature) will return [init-functions sensor-functions].
138 The init-functions are each single-arg functions that take the
139 world and register the cameras and must each be called before the
140 corresponding sensor-functions. Each init-function returns the
141 viewport for that eye which can be manipulated, saved, etc. Each
142 sensor-function is a thunk and will return data in the same
143 format as the tactile-sensor functions the structure is
144 [topology, sensor-data]. Internally, these sensor-functions
145 maintain a reference to sensor-data which is periodically updated
146 by the continuation function established by its init-function.
147 They can be queried every cycle, but their information may not
148 necessairly be different every cycle.
150 * Physical Eyes
152 The vision pipeline described above handles the flow of rendered
153 images. Now, we need simulated eyes to serve as the source of these
154 images.
156 An eye is described in blender in the same way as a joint. They are
157 zero dimensional empty objects with no geometry whose local coordinate
158 system determines the orientation of the resulting eye. All eyes are
159 childern of a parent node named "eyes" just as all joints have a
160 parent named "joints". An eye binds to the nearest physical object
161 with =(bind-sense=).
163 #+name: add-eye
164 #+begin_src clojure
165 (in-ns 'cortex.vision)
167 (defn add-eye!
168 "Create a Camera centered on the current position of 'eye which
169 follows the closest physical node in 'creature and sends visual
170 data to 'continuation. The camera will point in the X direction and
171 use the Z vector as up as determined by the rotation of these
172 vectors in blender coordinate space. Use XZY rotation for the node
173 in blender."
174 [#^Node creature #^Spatial eye]
175 (let [target (closest-node creature eye)
176 [cam-width cam-height] (eye-dimensions eye)
177 cam (Camera. cam-width cam-height)
178 rot (.getWorldRotation eye)]
179 (.setLocation cam (.getWorldTranslation eye))
180 (.lookAtDirection
181 cam ; this part is not a mistake and
182 (.mult rot Vector3f/UNIT_X) ; is consistent with using Z in
183 (.mult rot Vector3f/UNIT_Y)) ; blender as the UP vector.
184 (.setFrustumPerspective
185 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000)
186 (bind-sense target cam) cam))
187 #+end_src
189 Here, the camera is created based on metadata on the eye-node and
190 attached to the nearest physical object with =(bind-sense)=
193 ** The Retina
195 An eye is a surface (the retina) which contains many discrete sensors
196 to detect light. These sensors have can have different light-sensing
197 properties. In humans, each discrete sensor is sensitive to red,
198 blue, green, or gray. These different types of sensors can have
199 different spatial distributions along the retina. In humans, there is
200 a fovea in the center of the retina which has a very high density of
201 color sensors, and a blind spot which has no sensors at all. Sensor
202 density decreases in proportion to distance from the fovea.
204 I want to be able to model any retinal configuration, so my eye-nodes
205 in blender contain metadata pointing to images that describe the
206 percise position of the individual sensors using white pixels. The
207 meta-data also describes the percise sensitivity to light that the
208 sensors described in the image have. An eye can contain any number of
209 these images. For example, the metadata for an eye might look like
210 this:
212 #+begin_src clojure
213 {0xFF0000 "Models/test-creature/retina-small.png"}
214 #+end_src
216 #+caption: The retinal profile image "Models/test-creature/retina-small.png". White pixels are photo-sensitive elements. The distribution of white pixels is denser in the middle and falls off at the edges and is inspired by the human retina.
217 [[../assets/Models/test-creature/retina-small.png]]
219 Together, the number 0xFF0000 and the image image above describe the
220 placement of red-sensitive sensory elements.
222 Meta-data to very crudely approximate a human eye might be something
223 like this:
225 #+begin_src clojure
226 (let [retinal-profile "Models/test-creature/retina-small.png"]
227 {0xFF0000 retinal-profile
228 0x00FF00 retinal-profile
229 0x0000FF retinal-profile
230 0xFFFFFF retinal-profile})
231 #+end_src
233 The numbers that serve as keys in the map determine a sensor's
234 relative sensitivity to the channels red, green, and blue. These
235 sensitivity values are packed into an integer in the order =|_|R|G|B|=
236 in 8-bit fields. The RGB values of a pixel in the image are added
237 together with these sensitivities as linear weights. Therfore,
238 0xFF0000 means sensitive to red only while 0xFFFFFF means sensitive to
239 all colors equally (gray).
241 For convienence I've defined a few symbols for the more common
242 sensitivity values.
244 #+name: sensitivity
245 #+begin_src clojure
246 (defvar sensitivity-presets
247 {:all 0xFFFFFF
248 :red 0xFF0000
249 :blue 0x0000FF
250 :green 0x00FF00}
251 "Retinal sensitivity presets for sensors that extract one channel
252 (:red :blue :green) or average all channels (:all)")
253 #+end_src
255 ** Metadata Processing
257 =(retina-sensor-profile)= extracts a map from the eye-node in the same
258 format as the example maps above. =(eye-dimensions)= finds the
259 dimensions of the smallest image required to contain all the retinal
260 sensor maps.
262 #+name: retina
263 #+begin_src clojure
264 (defn retina-sensor-profile
265 "Return a map of pixel sensitivity numbers to BufferedImages
266 describing the distribution of light-sensitive components of this
267 eye. :red, :green, :blue, :gray are already defined as extracting
268 the red, green, blue, and average components respectively."
269 [#^Spatial eye]
270 (if-let [eye-map (meta-data eye "eye")]
271 (map-vals
272 load-image
273 (eval (read-string eye-map)))))
275 (defn eye-dimensions
276 "Returns [width, height] determined by the metadata of the eye."
277 [#^Spatial eye]
278 (let [dimensions
279 (map #(vector (.getWidth %) (.getHeight %))
280 (vals (retina-sensor-profile eye)))]
281 [(apply max (map first dimensions))
282 (apply max (map second dimensions))]))
283 #+end_src
285 * Eye Creation
286 First off, get the children of the "eyes" empty node to find all the
287 eyes the creature has.
288 #+name: eye-node
289 #+begin_src clojure
290 (defvar
291 ^{:arglists '([creature])}
292 eyes
293 (sense-nodes "eyes")
294 "Return the children of the creature's \"eyes\" node.")
295 #+end_src
297 Then, add the camera created by =(add-eye!)= to the simulation by
298 creating a new viewport.
300 #+name: add-camera
301 #+begin_src clojure
302 (defn add-camera!
303 "Add a camera to the world, calling continuation on every frame
304 produced."
305 [#^Application world camera continuation]
306 (let [width (.getWidth camera)
307 height (.getHeight camera)
308 render-manager (.getRenderManager world)
309 viewport (.createMainView render-manager "eye-view" camera)]
310 (doto viewport
311 (.setClearFlags true true true)
312 (.setBackgroundColor ColorRGBA/Black)
313 (.addProcessor (vision-pipeline continuation))
314 (.attachScene (.getRootNode world)))))
315 #+end_src
318 The eye's continuation function should register the viewport with the
319 simulation the first time it is called, use the CPU to extract the
320 appropriate pixels from the rendered image and weight them by each
321 sensor's sensitivity. I have the option to do this processing in
322 native code for a slight gain in speed. I could also do it in the GPU
323 for a massive gain in speed. =(vision-kernel)= generates a list of
324 such continuation functions, one for each channel of the eye.
326 #+name: kernel
327 #+begin_src clojure
328 (in-ns 'cortex.vision)
330 (defrecord attached-viewport [vision-fn viewport-fn]
331 clojure.lang.IFn
332 (invoke [this world] (vision-fn world))
333 (applyTo [this args] (apply vision-fn args)))
335 (defn pixel-sense [sensitivity pixel]
336 (let [s-r (bit-shift-right (bit-and 0xFF0000 sensitivity) 16)
337 s-g (bit-shift-right (bit-and 0x00FF00 sensitivity) 8)
338 s-b (bit-and 0x0000FF sensitivity)
340 p-r (bit-shift-right (bit-and 0xFF0000 pixel) 16)
341 p-g (bit-shift-right (bit-and 0x00FF00 pixel) 8)
342 p-b (bit-and 0x0000FF pixel)
344 total-sensitivity (* 255 (+ s-r s-g s-b))]
345 (float (/ (+ (* s-r p-r)
346 (* s-g p-g)
347 (* s-b p-b))
348 total-sensitivity))))
350 (defn vision-kernel
351 "Returns a list of functions, each of which will return a color
352 channel's worth of visual information when called inside a running
353 simulation."
354 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
355 (let [retinal-map (retina-sensor-profile eye)
356 camera (add-eye! creature eye)
357 vision-image
358 (atom
359 (BufferedImage. (.getWidth camera)
360 (.getHeight camera)
361 BufferedImage/TYPE_BYTE_BINARY))
362 register-eye!
363 (runonce
364 (fn [world]
365 (add-camera!
366 world camera
367 (let [counter (atom 0)]
368 (fn [r fb bb bi]
369 (if (zero? (rem (swap! counter inc) (inc skip)))
370 (reset! vision-image
371 (BufferedImage! r fb bb bi))))))))]
372 (vec
373 (map
374 (fn [[key image]]
375 (let [whites (white-coordinates image)
376 topology (vec (collapse whites))
377 sensitivity (sensitivity-presets key key)]
378 (attached-viewport.
379 (fn [world]
380 (register-eye! world)
381 (vector
382 topology
383 (vec
384 (for [[x y] whites]
385 (pixel-sense
386 sensitivity
387 (.getRGB @vision-image x y))))))
388 register-eye!)))
389 retinal-map))))
391 (defn gen-fix-display
392 "Create a function to call to restore a simulation's display when it
393 is disrupted by a Viewport."
394 []
395 (runonce
396 (fn [world]
397 (add-camera! world (.getCamera world) no-op))))
398 #+end_src
400 Note that since each of the functions generated by =(vision-kernel)=
401 shares the same =(register-eye!)= function, the eye will be registered
402 only once the first time any of the functions from the list returned
403 by =(vision-kernel)= is called. Each of the functions returned by
404 =(vision-kernel)= also allows access to the =Viewport= through which
405 it recieves images.
407 The in-game display can be disrupted by all the viewports that the
408 functions greated by =(vision-kernel)= add. This doesn't affect the
409 simulation or the simulated senses, but can be annoying.
410 =(gen-fix-display)= restores the in-simulation display.
412 ** Vision!
414 All the hard work has been done; all that remains is to apply
415 =(vision-kernel)= to each eye in the creature and gather the results
416 into one list of functions.
418 #+name: main
419 #+begin_src clojure
420 (defn vision!
421 "Returns a function which returns visual sensory data when called
422 inside a running simulation."
423 [#^Node creature & {skip :skip :or {skip 0}}]
424 (reduce
425 concat
426 (for [eye (eyes creature)]
427 (vision-kernel creature eye))))
428 #+end_src
430 ** Visualization of Vision
432 It's vital to have a visual representation for each sense. Here I use
433 =(view-sense)= to construct a function that will create a display for
434 visual data.
436 #+name: display
437 #+begin_src clojure
438 (in-ns 'cortex.vision)
440 (defn view-vision
441 "Creates a function which accepts a list of visual sensor-data and
442 displays each element of the list to the screen."
443 []
444 (view-sense
445 (fn
446 [[coords sensor-data]]
447 (let [image (points->image coords)]
448 (dorun
449 (for [i (range (count coords))]
450 (.setRGB image ((coords i) 0) ((coords i) 1)
451 (gray (int (* 255 (sensor-data i)))))))
452 image))))
453 #+end_src
455 * Tests
456 ** Basic Test
458 This is a basic test for the vision system. It only tests the
459 vision-pipeline and does not deal with loadig eyes from a blender
460 file. The code creates two videos of the same rotating cube from
461 different angles.
463 #+name: test-1
464 #+begin_src clojure
465 (in-ns 'cortex.test.vision)
467 (defn test-pipeline
468 "Testing vision:
469 Tests the vision system by creating two views of the same rotating
470 object from different angles and displaying both of those views in
471 JFrames.
473 You should see a rotating cube, and two windows,
474 each displaying a different view of the cube."
475 []
476 (let [candy
477 (box 1 1 1 :physical? false :color ColorRGBA/Blue)]
478 (world
479 (doto (Node.)
480 (.attachChild candy))
481 {}
482 (fn [world]
483 (let [cam (.clone (.getCamera world))
484 width (.getWidth cam)
485 height (.getHeight cam)]
486 (add-camera! world cam
487 (comp
488 (view-image
489 (File. "/home/r/proj/cortex/render/vision/1"))
490 BufferedImage!))
491 (add-camera! world
492 (doto (.clone cam)
493 (.setLocation (Vector3f. -10 0 0))
494 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
495 (comp
496 (view-image
497 (File. "/home/r/proj/cortex/render/vision/2"))
498 BufferedImage!))
499 ;; This is here to restore the main view
500 ;; after the other views have completed processing
501 (add-camera! world (.getCamera world) no-op)))
502 (fn [world tpf]
503 (.rotate candy (* tpf 0.2) 0 0)))))
504 #+end_src
506 #+begin_html
507 <div class="figure">
508 <video controls="controls" width="755">
509 <source src="../video/spinning-cube.ogg" type="video/ogg"
510 preload="none" poster="../images/aurellem-1280x480.png" />
511 </video>
512 <p>A rotating cube viewed from two different perspectives.</p>
513 </div>
514 #+end_html
516 Creating multiple eyes like this can be used for stereoscopic vision
517 simulation in a single creature or for simulating multiple creatures,
518 each with their own sense of vision.
520 ** Adding Vision to the Worm
522 To the worm from the last post, I add a new node that describes its
523 eyes.
525 #+attr_html: width=755
526 #+caption: The worm with newly added empty nodes describing a single eye.
527 [[../images/worm-with-eye.png]]
529 The node highlighted in yellow is the root level "eyes" node. It has
530 a single child, highlighted in orange, which describes a single
531 eye. This is the "eye" node. It is placed so that the worm will have
532 an eye located in the center of the flat portion of its lower
533 hemispherical section.
535 The two nodes which are not highlighted describe the single joint of
536 the worm.
538 The metadata of the eye-node is:
540 #+begin_src clojure :results verbatim :exports both
541 (cortex.sense/meta-data
542 (.getChild (.getChild (cortex.test.body/worm) "eyes") "eye") "eye")
543 #+end_src
545 #+results:
546 : "(let [retina \"Models/test-creature/retina-small.png\"]
547 : {:all retina :red retina :green retina :blue retina})"
549 This is the approximation to the human eye described earlier.
551 #+name: test-2
552 #+begin_src clojure
553 (in-ns 'cortex.test.vision)
555 (defn change-color [obj color]
556 (println-repl obj)
557 (if obj
558 (.setColor (.getMaterial obj) "Color" color)))
560 (defn colored-cannon-ball [color]
561 (comp #(change-color % color)
562 (fire-cannon-ball)))
564 (defn test-worm-vision [record]
565 (let [the-worm (doto (worm)(body!))
566 vision (vision! the-worm)
567 vision-display (view-vision)
568 fix-display (gen-fix-display)
569 me (sphere 0.5 :color ColorRGBA/Blue :physical? false)
570 x-axis
571 (box 1 0.01 0.01 :physical? false :color ColorRGBA/Red
572 :position (Vector3f. 0 -5 0))
573 y-axis
574 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green
575 :position (Vector3f. 0 -5 0))
576 z-axis
577 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue
578 :position (Vector3f. 0 -5 0))
579 timer (RatchetTimer. 60)]
581 (world (nodify [(floor) the-worm x-axis y-axis z-axis me])
582 (assoc standard-debug-controls
583 "key-r" (colored-cannon-ball ColorRGBA/Red)
584 "key-b" (colored-cannon-ball ColorRGBA/Blue)
585 "key-g" (colored-cannon-ball ColorRGBA/Green))
586 (fn [world]
587 (light-up-everything world)
588 (speed-up world)
589 (.setTimer world timer)
590 (display-dialated-time world timer)
591 ;; add a view from the worm's perspective
592 (if record
593 (Capture/captureVideo
594 world
595 (File.
596 "/home/r/proj/cortex/render/worm-vision/main-view")))
598 (add-camera!
599 world
600 (add-eye! the-worm
601 (.getChild
602 (.getChild the-worm "eyes") "eye"))
603 (comp
604 (view-image
605 (if record
606 (File.
607 "/home/r/proj/cortex/render/worm-vision/worm-view")))
608 BufferedImage!))
610 (set-gravity world Vector3f/ZERO))
612 (fn [world _ ]
613 (.setLocalTranslation me (.getLocation (.getCamera world)))
614 (vision-display
615 (map #(% world) vision)
616 (if record (File. "/home/r/proj/cortex/render/worm-vision")))
617 (fix-display world)))))
618 #+end_src
620 The world consists of the worm and a flat gray floor. I can shoot red,
621 green, blue and white cannonballs at the worm. The worm is initially
622 looking down at the floor, and there is no gravity. My perspective
623 (the Main View), the worm's perspective (Worm View) and the 4 sensor
624 channels that comprise the worm's eye are all saved frame-by-frame to
625 disk.
627 * Demonstration of Vision
628 #+begin_html
629 <div class="figure">
630 <video controls="controls" width="755">
631 <source src="../video/worm-vision.ogg" type="video/ogg"
632 preload="none" poster="../images/aurellem-1280x480.png" />
633 </video>
634 <p>Simulated Vision in a Virtual Environment</p>
635 </div>
636 #+end_html
638 ** Generate the Worm Video from Frames
639 #+name: magick2
640 #+begin_src clojure
641 (ns cortex.video.magick2
642 (:import java.io.File)
643 (:use clojure.contrib.shell-out))
645 (defn images [path]
646 (sort (rest (file-seq (File. path)))))
648 (def base "/home/r/proj/cortex/render/worm-vision/")
650 (defn pics [file]
651 (images (str base file)))
653 (defn combine-images []
654 (let [main-view (pics "main-view")
655 worm-view (pics "worm-view")
656 blue (pics "0")
657 green (pics "1")
658 red (pics "2")
659 gray (pics "3")
660 blender (let [b-pics (pics "blender")]
661 (concat b-pics (repeat 9001 (last b-pics))))
662 background (repeat 9001 (File. (str base "background.png")))
663 targets (map
664 #(File. (str base "out/" (format "%07d.png" %)))
665 (range 0 (count main-view)))]
666 (dorun
667 (pmap
668 (comp
669 (fn [[background main-view worm-view red green blue gray blender target]]
670 (println target)
671 (sh "convert"
672 background
673 main-view "-geometry" "+18+17" "-composite"
674 worm-view "-geometry" "+677+17" "-composite"
675 green "-geometry" "+685+430" "-composite"
676 red "-geometry" "+788+430" "-composite"
677 blue "-geometry" "+894+430" "-composite"
678 gray "-geometry" "+1000+430" "-composite"
679 blender "-geometry" "+0+0" "-composite"
680 target))
681 (fn [& args] (map #(.getCanonicalPath %) args)))
682 background main-view worm-view red green blue gray blender targets))))
683 #+end_src
685 #+begin_src sh :results silent
686 cd /home/r/proj/cortex/render/worm-vision
687 ffmpeg -r 25 -b 9001k -i out/%07d.png -vcodec libtheora worm-vision.ogg
688 #+end_src
690 * Headers
692 #+name: vision-header
693 #+begin_src clojure
694 (ns cortex.vision
695 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
696 eyes from different positions to observe the same world, and pass
697 the observed data to any arbitray function. Automatically reads
698 eye-nodes from specially prepared blender files and instantiates
699 them in the world as actual eyes."
700 {:author "Robert McIntyre"}
701 (:use (cortex world sense util))
702 (:use clojure.contrib.def)
703 (:import com.jme3.post.SceneProcessor)
704 (:import (com.jme3.util BufferUtils Screenshots))
705 (:import java.nio.ByteBuffer)
706 (:import java.awt.image.BufferedImage)
707 (:import (com.jme3.renderer ViewPort Camera))
708 (:import (com.jme3.math ColorRGBA Vector3f Matrix3f))
709 (:import com.jme3.renderer.Renderer)
710 (:import com.jme3.app.Application)
711 (:import com.jme3.texture.FrameBuffer)
712 (:import (com.jme3.scene Node Spatial)))
713 #+end_src
715 #+name: test-header
716 #+begin_src clojure
717 (ns cortex.test.vision
718 (:use (cortex world sense util body vision))
719 (:use cortex.test.body)
720 (:import java.awt.image.BufferedImage)
721 (:import javax.swing.JPanel)
722 (:import javax.swing.SwingUtilities)
723 (:import java.awt.Dimension)
724 (:import javax.swing.JFrame)
725 (:import com.jme3.math.ColorRGBA)
726 (:import com.jme3.scene.Node)
727 (:import com.jme3.math.Vector3f)
728 (:import java.io.File)
729 (:import (com.aurellem.capture Capture RatchetTimer)))
730 #+end_src
732 * Onward!
733 - As a neat bonus, this idea behind simulated vision also enables one
734 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
735 - Now that we have vision, it's time to tackle [[./hearing.org][hearing]].
737 * Source Listing
738 - [[../src/cortex/vision.clj][cortex.vision]]
739 - [[../src/cortex/test/vision.clj][cortex.test.vision]]
740 - [[../src/cortex/video/magick2.clj][cortex.video.magick2]]
741 - [[../assets/Models/subtitles/worm-vision-subtitles.blend][worm-vision-subtitles.blend]]
742 #+html: <ul> <li> <a href="../org/sense.org">This org file</a> </li> </ul>
743 - [[http://hg.bortreb.com ][source-repository]]
747 * COMMENT Generate Source
748 #+begin_src clojure :tangle ../src/cortex/vision.clj
749 <<vision-header>>
750 <<pipeline-1>>
751 <<pipeline-2>>
752 <<retina>>
753 <<add-eye>>
754 <<sensitivity>>
755 <<eye-node>>
756 <<add-camera>>
757 <<kernel>>
758 <<main>>
759 <<display>>
760 #+end_src
762 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
763 <<test-header>>
764 <<test-1>>
765 <<test-2>>
766 #+end_src
768 #+begin_src clojure :tangle ../src/cortex/video/magick2.clj
769 <<magick2>>
770 #+end_src