Mercurial > cortex
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 |