comparison org/vision.org @ 215:f283c62bd212

fixed long standing problem with orientation of eyes in blender, fleshed out text in vision.org
author Robert McIntyre <rlm@mit.edu>
date Fri, 10 Feb 2012 02:19:24 -0700
parents 01d3e9855ef9
children f5ea63245b3b
comparison
equal deleted inserted replaced
214:01d3e9855ef9 215:f283c62bd212
158 parent named "joints". An eye binds to the nearest physical object 158 parent named "joints". An eye binds to the nearest physical object
159 with =(bind-sense=). 159 with =(bind-sense=).
160 160
161 #+name: add-eye 161 #+name: add-eye
162 #+begin_src clojure 162 #+begin_src clojure
163 (in-ns 'cortex.vision)
164
165 (import com.jme3.math.Vector3f)
166
167 (def blender-rotation-correction
168 (doto (Quaternion.)
169 (.fromRotationMatrix
170 (doto (Matrix3f.)
171 (.setColumn 0
172 (Vector3f. 1 0 0))
173 (.setColumn 1
174 (Vector3f. 0 -1 0))
175 (.setColumn 2
176 (Vector3f. 0 0 -1)))
177
178 (doto (Matrix3f.)
179 (.setColumn 0
180 (Vector3f.
181
182
163 (defn add-eye! 183 (defn add-eye!
164 "Create a Camera centered on the current position of 'eye which 184 "Create a Camera centered on the current position of 'eye which
165 follows the closest physical node in 'creature and sends visual 185 follows the closest physical node in 'creature and sends visual
166 data to 'continuation." 186 data to 'continuation. The camera will point in the X direction and
187 use the Z vector as up as determined by the rotation of these
188 vectors in blender coordinate space. Use XZY rotation for the node
189 in blender."
167 [#^Node creature #^Spatial eye] 190 [#^Node creature #^Spatial eye]
168 (let [target (closest-node creature eye) 191 (let [target (closest-node creature eye)
169 [cam-width cam-height] (eye-dimensions eye) 192 [cam-width cam-height] (eye-dimensions eye)
170 cam (Camera. cam-width cam-height)] 193 cam (Camera. cam-width cam-height)
194 rot (.getWorldRotation eye)]
171 (.setLocation cam (.getWorldTranslation eye)) 195 (.setLocation cam (.getWorldTranslation eye))
172 (.setRotation cam (.getWorldRotation eye)) 196 (.lookAtDirection cam (.mult rot Vector3f/UNIT_X)
197 ;; this part is consistent with using Z in
198 ;; blender as the UP vector.
199 (.mult rot Vector3f/UNIT_Y))
200
201 (println-repl "eye unit-z ->" (.mult rot Vector3f/UNIT_Z))
202 (println-repl "eye unit-y ->" (.mult rot Vector3f/UNIT_Y))
203 (println-repl "eye unit-x ->" (.mult rot Vector3f/UNIT_X))
173 (.setFrustumPerspective 204 (.setFrustumPerspective
174 cam 45 (/ (.getWidth cam) (.getHeight cam)) 205 cam 45 (/ (.getWidth cam) (.getHeight cam)) 1 1000)
175 1 1000) 206 (bind-sense target cam) cam))
176 (bind-sense target cam)
177 cam))
178 #+end_src 207 #+end_src
179 208
180 Here, the camera is created based on metadata on the eye-node and 209 Here, the camera is created based on metadata on the eye-node and
181 attached to the nearest physical object with =(bind-sense)= 210 attached to the nearest physical object with =(bind-sense)=
182 211
284 eyes 313 eyes
285 (sense-nodes "eyes") 314 (sense-nodes "eyes")
286 "Return the children of the creature's \"eyes\" node.") 315 "Return the children of the creature's \"eyes\" node.")
287 #+end_src 316 #+end_src
288 317
289 Then, 318 Then, add the camera created by =(add-eye!)= to the simulation by
319 creating a new viewport.
290 320
291 #+begin_src clojure 321 #+begin_src clojure
292 (defn add-camera! 322 (defn add-camera!
293 "Add a camera to the world, calling continuation on every frame 323 "Add a camera to the world, calling continuation on every frame
294 produced." 324 produced."
300 (doto viewport 330 (doto viewport
301 (.setClearFlags true true true) 331 (.setClearFlags true true true)
302 (.setBackgroundColor ColorRGBA/Black) 332 (.setBackgroundColor ColorRGBA/Black)
303 (.addProcessor (vision-pipeline continuation)) 333 (.addProcessor (vision-pipeline continuation))
304 (.attachScene (.getRootNode world))))) 334 (.attachScene (.getRootNode world)))))
305 335 #+end_src
306 336
307 337
308 338 The continuation function registers the viewport with the simulation
309 339 the first time it is called, and uses the CPU to extract the
310 (defn vision-fn 340 appropriate pixels from the rendered image and weight them by each
341 sensors sensitivity. I have the option to do this filtering in native
342 code for a slight gain in speed. I could also do it in the GPU for a
343 massive gain in speed. =(vision-kernel)= generates a list of such
344 continuation functions, one for each channel of the eye.
345
346 #+begin_src clojure
347 (in-ns 'cortex.vision)
348
349 (defrecord attached-viewport [vision-fn viewport-fn]
350 clojure.lang.IFn
351 (invoke [this world] (vision-fn world))
352 (applyTo [this args] (apply vision-fn args)))
353
354 (defn vision-kernel
311 "Returns a list of functions, each of which will return a color 355 "Returns a list of functions, each of which will return a color
312 channel's worth of visual information when called inside a running 356 channel's worth of visual information when called inside a running
313 simulation." 357 simulation."
314 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}] 358 [#^Node creature #^Spatial eye & {skip :skip :or {skip 0}}]
315 (let [retinal-map (retina-sensor-profile eye) 359 (let [retinal-map (retina-sensor-profile eye)
333 (map 377 (map
334 (fn [[key image]] 378 (fn [[key image]]
335 (let [whites (white-coordinates image) 379 (let [whites (white-coordinates image)
336 topology (vec (collapse whites)) 380 topology (vec (collapse whites))
337 mask (color-channel-presets key key)] 381 mask (color-channel-presets key key)]
338 (fn [world] 382 (attached-viewport.
339 (register-eye! world) 383 (fn [world]
340 (vector 384 (register-eye! world)
341 topology 385 (vector
342 (vec 386 topology
343 (for [[x y] whites] 387 (vec
344 (bit-and 388 (for [[x y] whites]
345 mask (.getRGB @vision-image x y)))))))) 389 (bit-and
346 retinal-map)))) 390 mask (.getRGB @vision-image x y))))))
347 391 register-eye!)))
348 392 retinal-map))))
349 ;; TODO maybe should add a viewport-manipulation function to 393
350 ;; automatically change viewport settings, attach shadow filters, etc. 394 (defn gen-fix-display
351 395 "Create a function to call to restore a simulation's display when it
396 is disrupted by a Viewport."
397 []
398 (runonce
399 (fn [world]
400 (add-camera! world (.getCamera world) no-op))))
401
402 #+end_src
403
404 Note that since each of the functions generated by =(vision-kernel)=
405 shares the same =(register-eye!)= function, the eye will be registered
406 only once the first time any of the functions from the list returned
407 by =(vision-kernel)= is called. Each of the functions returned by
408 =(vision-kernel)= also allows access to the =Viewport= through which
409 it recieves images.
410
411 The in-game display can be disrupted by all the viewports that the
412 functions greated by =(vision-kernel)= add. This doesn't affect the
413 simulation or the simulated senses, but can be annoying.
414 =(gen-fix-display)= restores the in-simulation display.
415
416 ** Vision!
417
418 All the hard work has been done, all that remains is to apply
419 =(vision-kernel)= to each eye in the creature and gather the results
420 into one list of functions.
421
422 #+begin_src clojure
352 (defn vision! 423 (defn vision!
353 "Returns a function which returns visual sensory data when called 424 "Returns a function which returns visual sensory data when called
354 inside a running simulation" 425 inside a running simulation"
355 [#^Node creature & {skip :skip :or {skip 0}}] 426 [#^Node creature & {skip :skip :or {skip 0}}]
356 (reduce 427 (reduce
357 concat 428 concat
358 (for [eye (eyes creature)] 429 (for [eye (eyes creature)]
359 (vision-fn creature eye)))) 430 (vision-kernel creature eye))))
360 431 #+end_src
432
433 ** Visualization of Vision
434
435 It's vital to have a visual representation for each sense. Here I use
436 =(view-sense)= to construct a function that will create a display for
437 visual data.
438
439 #+begin_src clojure
361 (defn view-vision 440 (defn view-vision
362 "Creates a function which accepts a list of visual sensor-data and 441 "Creates a function which accepts a list of visual sensor-data and
363 displays each element of the list to the screen." 442 displays each element of the list to the screen."
364 [] 443 []
365 (view-sense 444 (view-sense
369 (dorun 448 (dorun
370 (for [i (range (count coords))] 449 (for [i (range (count coords))]
371 (.setRGB image ((coords i) 0) ((coords i) 1) 450 (.setRGB image ((coords i) 0) ((coords i) 1)
372 (sensor-data i)))) 451 (sensor-data i))))
373 image)))) 452 image))))
374 453 #+end_src
375 #+end_src 454
376 455 * Tests
377 456
378 Note the use of continuation passing style for connecting the eye to a 457 ** Basic Test
379 function to process the output. You can create any number of eyes, and 458
380 each of them will see the world from their own =Camera=. Once every 459 This is a basic test for the vision system. It only tests the
381 frame, the rendered image is copied to a =BufferedImage=, and that 460 vision-pipeline and does not deal with loadig eyes from a blender
382 data is sent off to the continuation function. Moving the =Camera= 461 file. The code creates two videos of the same rotating cube from
383 which was used to create the eye will change what the eye sees. 462 different angles.
384 463
385 * Example 464 #+name: test-1
386 465 #+begin_src clojure
387 #+name: test-vision 466 (in-ns 'cortex.test.vision)
388 #+begin_src clojure
389 (ns cortex.test.vision
390 (:use (cortex world util vision))
391 (:import java.awt.image.BufferedImage)
392 (:import javax.swing.JPanel)
393 (:import javax.swing.SwingUtilities)
394 (:import java.awt.Dimension)
395 (:import javax.swing.JFrame)
396 (:import com.jme3.math.ColorRGBA)
397 (:import com.jme3.scene.Node)
398 (:import com.jme3.math.Vector3f))
399 467
400 (defn test-two-eyes 468 (defn test-two-eyes
401 "Testing vision: 469 "Testing vision:
402 Tests the vision system by creating two views of the same rotating 470 Tests the vision system by creating two views of the same rotating
403 object from different angles and displaying both of those views in 471 object from different angles and displaying both of those views in
415 (fn [world] 483 (fn [world]
416 (let [cam (.clone (.getCamera world)) 484 (let [cam (.clone (.getCamera world))
417 width (.getWidth cam) 485 width (.getWidth cam)
418 height (.getHeight cam)] 486 height (.getHeight cam)]
419 (add-camera! world cam 487 (add-camera! world cam
420 ;;no-op 488 (comp
421 (comp (view-image) BufferedImage!) 489 (view-image
422 ) 490 (File. "/home/r/proj/cortex/render/vision/1"))
491 BufferedImage!))
423 (add-camera! world 492 (add-camera! world
424 (doto (.clone cam) 493 (doto (.clone cam)
425 (.setLocation (Vector3f. -10 0 0)) 494 (.setLocation (Vector3f. -10 0 0))
426 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y)) 495 (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))
427 ;;no-op 496 (comp
428 (comp (view-image) BufferedImage!)) 497 (view-image
498 (File. "/home/r/proj/cortex/render/vision/2"))
499 BufferedImage!))
429 ;; This is here to restore the main view 500 ;; This is here to restore the main view
430 ;; after the other views have completed processing 501 ;; after the other views have completed processing
431 (add-camera! world (.getCamera world) no-op))) 502 (add-camera! world (.getCamera world) no-op)))
432 (fn [world tpf] 503 (fn [world tpf]
433 (.rotate candy (* tpf 0.2) 0 0))))) 504 (.rotate candy (* tpf 0.2) 0 0)))))
434 #+end_src 505 #+end_src
506
507 #+begin_html
508 <div class="figure">
509 <video controls="controls" width="755">
510 <source src="../video/spinning-cube.ogg" type="video/ogg"
511 preload="none" poster="../images/aurellem-1280x480.png" />
512 </video>
513 <p>A rotating cube viewed from two different perspectives.</p>
514 </div>
515 #+end_html
516
517 Creating multiple eyes like this can be used for stereoscopic vision
518 simulation in a single creature or for simulating multiple creatures,
519 each with their own sense of vision.
520
521 ** Adding Vision to the Worm
522
523 To the worm from the last post, we add a new node that describes its
524 eyes.
525
526 #+attr_html: width=755
527 #+caption: The worm with newly added empty nodes describing a single eye.
528 [[../images/worm-with-eye.png]]
529
530 The node highlighted in yellow is the root level "eyes" node. It has
531 a single node, highlighted in orange, which describes a single
532 eye. This is the "eye" node. The two nodes which are not highlighted describe the single joint
533 of the worm.
534
535 The metadata of the eye-node is:
536
537 #+begin_src clojure :results verbatim :exports both
538 (cortex.sense/meta-data
539 (.getChild
540 (.getChild (cortex.test.body/worm)
541 "eyes") "eye") "eye")
542 #+end_src
543
544 #+results:
545 : "(let [retina \"Models/test-creature/retina-small.png\"]
546 : {:all retina :red retina :green retina :blue retina})"
547
548 This is the approximation to the human eye described earlier.
549
550 #+begin_src clojure
551 (in-ns 'cortex.test.vision)
552
553 (import com.aurellem.capture.Capture)
554
555 (defn test-worm-vision []
556 (let [the-worm (doto (worm)(body!))
557 vision (vision! the-worm)
558 vision-display (view-vision)
559 fix-display (gen-fix-display)
560 me (sphere 0.5 :color ColorRGBA/Blue :physical? false)
561 x-axis
562 (box 1 0.01 0.01 :physical? false :color ColorRGBA/Red
563 :position (Vector3f. 0 -5 0))
564 y-axis
565 (box 0.01 1 0.01 :physical? false :color ColorRGBA/Green
566 :position (Vector3f. 0 -5 0))
567 z-axis
568 (box 0.01 0.01 1 :physical? false :color ColorRGBA/Blue
569 :position (Vector3f. 0 -5 0))]
570
571 (world (nodify [(floor) the-worm x-axis y-axis z-axis me])
572 standard-debug-controls
573 (fn [world]
574 (light-up-everything world)
575 ;; add a view from the worm's perspective
576 (add-camera!
577 world
578 (add-eye! the-worm
579 (.getChild
580 (.getChild the-worm "eyes") "eye"))
581 (comp
582 (view-image
583 (File. "/home/r/proj/cortex/render/worm-vision/worm-view"))
584 BufferedImage!))
585 (set-gravity world Vector3f/ZERO)
586 (Capture/captureVideo
587 world
588 (File. "/home/r/proj/cortex/render/worm-vision/main-view")))
589 (fn [world _ ]
590 (.setLocalTranslation me (.getLocation (.getCamera world)))
591 (vision-display
592 (map #(% world) vision)
593 (File. "/home/r/proj/cortex/render/worm-vision"))
594 (fix-display world)))))
595 #+end_src
596
597 * Headers
435 598
436 #+name: vision-header 599 #+name: vision-header
437 #+begin_src clojure 600 #+begin_src clojure
438 (ns cortex.vision 601 (ns cortex.vision
439 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple 602 "Simulate the sense of vision in jMonkeyEngine3. Enables multiple
454 (:import com.jme3.app.Application) 617 (:import com.jme3.app.Application)
455 (:import com.jme3.texture.FrameBuffer) 618 (:import com.jme3.texture.FrameBuffer)
456 (:import (com.jme3.scene Node Spatial))) 619 (:import (com.jme3.scene Node Spatial)))
457 #+end_src 620 #+end_src
458 621
459 The example code will create two videos of the same rotating object 622 #+name: test-header
460 from different angles. It can be used both for stereoscopic vision 623 #+begin_src clojure
461 simulation or for simulating multiple creatures, each with their own 624 (ns cortex.test.vision
462 sense of vision. 625 (:use (cortex world sense util body vision))
626 (:use cortex.test.body)
627 (:import java.awt.image.BufferedImage)
628 (:import javax.swing.JPanel)
629 (:import javax.swing.SwingUtilities)
630 (:import java.awt.Dimension)
631 (:import javax.swing.JFrame)
632 (:import com.jme3.math.ColorRGBA)
633 (:import com.jme3.scene.Node)
634 (:import com.jme3.math.Vector3f)
635 (:import java.io.File))
636 #+end_src
637
638
463 639
464 - As a neat bonus, this idea behind simulated vision also enables one 640 - As a neat bonus, this idea behind simulated vision also enables one
465 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]]. 641 to [[../../cortex/html/capture-video.html][capture live video feeds from jMonkeyEngine]].
466 642
467 643
469 #+begin_src clojure :tangle ../src/cortex/vision.clj 645 #+begin_src clojure :tangle ../src/cortex/vision.clj
470 <<eyes>> 646 <<eyes>>
471 #+end_src 647 #+end_src
472 648
473 #+begin_src clojure :tangle ../src/cortex/test/vision.clj 649 #+begin_src clojure :tangle ../src/cortex/test/vision.clj
474 <<test-vision>> 650 <<test-header>>
475 #+end_src 651 <<test-1>>
652 #+end_src