view org/body.org @ 458:42ddfe406c0a

working on depth maps for the lulz.
author Robert McIntyre <rlm@mit.edu>
date Tue, 11 Jun 2013 07:13:42 -0400
parents 4c37d39a3cf6
children 8bf4bb02ed05
line wrap: on
line source
1 #+title: Building a Body
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description: Simulating a body (movement, touch, proprioception) in jMonkeyEngine3.
5 #+SETUPFILE: ../../aurellem/org/setup.org
6 #+INCLUDE: ../../aurellem/org/level-0.org
8 * Design Constraints
10 I use [[www.blender.org/][blender]] to design bodies. The design of the bodies is
11 determined by the requirements of the AI that will use them. The
12 bodies must be easy for an AI to sense and control, and they must be
13 relatively simple for jMonkeyEngine to compute.
15 # I'm a secret test! :P
16 ** Bag of Bones
18 How to create such a body? One option I ultimately rejected is to use
19 blender's [[http://wiki.blender.org/index.php/Doc:2.6/Manual/Rigging/Armatures][armature]] system. The idea would have been to define a mesh
20 which describes the creature's entire body. To this you add an
21 skeleton which deforms this mesh. This technique is used extensively
22 to model humans and create realistic animations. It is hard to use for
23 my purposes because it is difficult to update the creature's Physics
24 Collision Mesh in tandem with its Geometric Mesh under the influence
25 of the armature. Without this the creature will not be able to grab
26 things in its environment, and it won't be able to tell where its
27 physical body is by using its eyes. Also, armatures do not specify any
28 rotational limits for a joint, making it hard to model elbows,
29 shoulders, etc.
31 ** EVE
33 Instead of using the human-like "deformable bag of bones" approach, I
34 decided to base my body plans on the robot EVE from the movie wall-E.
36 #+caption: EVE from the movie WALL-E. This body plan turns out to be much better suited to my purposes than a more human-like one.
37 [[../images/Eve.jpg]]
39 EVE's body is composed of several rigid components that are held
40 together by invisible joint constraints. This is what I mean by
41 "eve-like". The main reason that I use eve-style bodies is so that
42 there will be correspondence between the AI's vision and the physical
43 presence of its body. Each individual section is simulated by a
44 separate rigid body that corresponds exactly with its visual
45 representation and does not change. Sections are connected by
46 invisible joints that are well supported in jMonkeyEngine. Bullet, the
47 physics backend for jMonkeyEngine, can efficiently simulate hundreds
48 of rigid bodies connected by joints. Sections do not have to stay as
49 one piece forever; they can be dynamically replaced with multiple
50 sections to simulate splitting in two. This could be used to simulate
51 retractable claws or EVE's hands, which are able to coalesce into one
52 object in the movie.
54 * Solidifying the Body
56 Here is a hand designed eve-style in blender.
58 #+attr_html: width="755"
59 [[../images/hand-screenshot0.png]]
61 If we load it directly into jMonkeyEngine, we get this:
63 #+name: test-1
64 #+begin_src clojure
65 (def hand-path "Models/test-creature/hand.blend")
67 (defn hand [] (load-blender-model hand-path))
69 (defn setup [world]
70 (let [cam (.getCamera world)]
71 (println-repl cam)
72 (.setLocation
73 cam (Vector3f.
74 -6.9015837, 8.644911, 5.6043186))
75 (.setRotation
76 cam
77 (Quaternion.
78 0.14046453, 0.85894054, -0.34301838, 0.3533118)))
79 (light-up-everything world)
80 (.setTimer world (RatchetTimer. 60))
81 world)
83 (defn test-hand-1
84 ([] (test-hand-1 false))
85 ([record?]
86 (world (hand)
87 standard-debug-controls
88 (fn [world]
89 (if record?
90 (Capture/captureVideo
91 world
92 (File. "/home/r/proj/cortex/render/body/1")))
93 (setup world)) no-op)))
94 #+end_src
97 #+begin_src clojure :results silent
98 (.start (cortex.test.body/test-one))
99 #+end_src
101 #+begin_html
102 <div class="figure">
103 <center>
104 <video controls="controls" width="640">
105 <source src="../video/ghost-hand.ogg" type="video/ogg"
106 preload="none" poster="../images/aurellem-1280x480.png" />
107 </video>
108 <br> <a href="http://youtu.be/9LZpwTIhjzE"> YouTube </a>
109 </center>
110 <p>The hand model directly loaded from blender. It has no physical
111 presence in the simulation. </p>
112 </div>
113 #+end_html
115 You will notice that the hand has no physical presence -- it's a
116 hologram through which everything passes. Therefore, the first thing
117 to do is to make it solid. Blender has physics simulation on par with
118 jMonkeyEngine (they both use bullet as their physics backend), but it
119 can be difficult to translate between the two systems, so for now I
120 specify the mass of each object as meta-data in blender and construct
121 the physics shape based on the mesh in jMonkeyEngine.
123 #+name: body-1
124 #+begin_src clojure
125 (defn physical!
126 "Iterate through the nodes in creature and make them real physical
127 objects in the simulation."
128 [#^Node creature]
129 (dorun
130 (map
131 (fn [geom]
132 (let [physics-control
133 (RigidBodyControl.
134 (HullCollisionShape.
135 (.getMesh geom))
136 (if-let [mass (meta-data geom "mass")]
137 (do
138 ;;(println-repl
139 ;; "setting" (.getName geom) "mass to" (float mass))
140 (float mass))
141 (float 1)))]
142 (.addControl geom physics-control)))
143 (filter #(isa? (class %) Geometry )
144 (node-seq creature)))))
145 #+end_src
147 =physical!= iterates through a creature's node structure, creating
148 CollisionShapes for each geometry with the mass specified in that
149 geometry's meta-data.
151 #+name: test-2
152 #+begin_src clojure
153 (in-ns 'cortex.test.body)
155 (def gravity-control
156 {"key-g" (fn [world _]
157 (set-gravity world (Vector3f. 0 -9.81 0)))
158 "key-u" (fn [world _] (set-gravity world Vector3f/ZERO))})
160 (defn floor []
161 (box 10 3 10 :position (Vector3f. 0 -10 0)
162 :color ColorRGBA/Gray :mass 0))
164 (defn test-hand-2
165 ([] (test-hand-2 false))
166 ([record?]
167 (world
168 (nodify
169 [(doto (hand)
170 (physical!))
171 (floor)])
172 (merge standard-debug-controls gravity-control)
173 (fn [world]
174 (if record?
175 (Capture/captureVideo
176 world (File. "/home/r/proj/cortex/render/body/2")))
177 (set-gravity world Vector3f/ZERO)
178 (setup world))
179 no-op)))
180 #+end_src
182 #+results: test-2
183 : #'cortex.test.body/test-hand-2
185 #+begin_html
186 <div class="figure">
187 <center>
188 <video controls="controls" width="640">
189 <source src="../video/crumbly-hand.ogg" type="video/ogg"
190 preload="none" poster="../images/aurellem-1280x480.png" />
191 </video>
192 <br> <a href="http://youtu.be/GEA1SACwpPg"> YouTube </a>
193 </center>
194 <p>The hand now has a physical presence, but there is nothing to hold
195 it together.</p>
196 </div>
197 #+end_html
199 Now that's some progress.
201 * Joints
203 Obviously, an AI is not going to be doing much while lying in pieces
204 on the floor. So, the next step to making a proper body is to connect
205 those pieces together with joints. jMonkeyEngine has a large array of
206 joints available via bullet, such as Point2Point, Cone, Hinge, and a
207 generic Six Degree of Freedom joint, with or without spring
208 restitution.
210 Although it should be possible to specify the joints using blender's
211 physics system, and then automatically import them with jMonkeyEngine,
212 the support isn't there yet, and there are a few problems with bullet
213 itself that need to be solved before it can happen.
215 So, I will use the same system for specifying joints as I will do for
216 some senses. Each joint is specified by an empty node whose parent
217 has the name "joints". Their orientation and meta-data determine what
218 joint is created.
220 #+attr_html: width="755"
221 #+caption: Joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine
222 [[../images/hand-screenshot1.png]]
224 The empty node in the upper right, highlighted in yellow, is the
225 parent node of all the empties which represent joints. The following
226 functions must do three things to translate these into real joints:
228 - Find the children of the "joints" node.
229 - Determine the two spatials the joint it meant to connect.
230 - Create the joint based on the meta-data of the empty node.
232 ** Finding the Joints
234 The higher order function =sense-nodes= from =cortex.sense= simplifies
235 the first task.
237 #+name: joints-2
238 #+begin_src clojure
239 (def
240 ^{:doc "Return the children of the creature's \"joints\" node."
241 :arglists '([creature])}
242 joints
243 (sense-nodes "joints"))
244 #+end_src
246 ** Joint Targets and Orientation
248 This technique for finding a joint's targets is very similar to
249 =cortex.sense/closest-node=. A small cube, centered around the
250 empty-node, grows exponentially until it intersects two /physical/
251 objects. The objects are ordered according to the joint's rotation,
252 with the first one being the object that has more negative coordinates
253 in the joint's reference frame. Since the objects must be physical,
254 the empty-node itself escapes detection. Because the objects must be
255 physical, =joint-targets= must be called /after/ =physical!= is
256 called.
258 #+name: joints-3
259 #+begin_src clojure
260 (defn joint-targets
261 "Return the two closest two objects to the joint object, ordered
262 from bottom to top according to the joint's rotation."
263 [#^Node parts #^Node joint]
264 (loop [radius (float 0.01)]
265 (let [results (CollisionResults.)]
266 (.collideWith
267 parts
268 (BoundingBox. (.getWorldTranslation joint)
269 radius radius radius) results)
270 (let [targets
271 (distinct
272 (map #(.getGeometry %) results))]
273 (if (>= (count targets) 2)
274 (sort-by
275 #(let [joint-ref-frame-position
276 (jme-to-blender
277 (.mult
278 (.inverse (.getWorldRotation joint))
279 (.subtract (.getWorldTranslation %)
280 (.getWorldTranslation joint))))]
281 (.dot (Vector3f. 1 1 1) joint-ref-frame-position))
282 (take 2 targets))
283 (recur (float (* radius 2))))))))
284 #+end_src
286 ** Generating Joints
288 This section of code iterates through all the different ways of
289 specifying joints using blender meta-data and converts each one to the
290 appropriate jMonkeyEngine joint.
292 #+name: joints-4
293 #+begin_src clojure
294 (defmulti joint-dispatch
295 "Translate blender pseudo-joints into real JME joints."
296 (fn [constraints & _]
297 (:type constraints)))
299 (defmethod joint-dispatch :point
300 [constraints control-a control-b pivot-a pivot-b rotation]
301 ;;(println-repl "creating POINT2POINT joint")
302 ;; bullet's point2point joints are BROKEN, so we must use the
303 ;; generic 6DOF joint instead of an actual Point2Point joint!
305 ;; should be able to do this:
306 (comment
307 (Point2PointJoint.
308 control-a
309 control-b
310 pivot-a
311 pivot-b))
313 ;; but instead we must do this:
314 ;;(println-repl "substituting 6DOF joint for POINT2POINT joint!")
315 (doto
316 (SixDofJoint.
317 control-a
318 control-b
319 pivot-a
320 pivot-b
321 false)
322 (.setLinearLowerLimit Vector3f/ZERO)
323 (.setLinearUpperLimit Vector3f/ZERO)))
325 (defmethod joint-dispatch :hinge
326 [constraints control-a control-b pivot-a pivot-b rotation]
327 ;;(println-repl "creating HINGE joint")
328 (let [axis
329 (if-let
330 [axis (:axis constraints)]
331 axis
332 Vector3f/UNIT_X)
333 [limit-1 limit-2] (:limit constraints)
334 hinge-axis
335 (.mult
336 rotation
337 (blender-to-jme axis))]
338 (doto
339 (HingeJoint.
340 control-a
341 control-b
342 pivot-a
343 pivot-b
344 hinge-axis
345 hinge-axis)
346 (.setLimit limit-1 limit-2))))
348 (defmethod joint-dispatch :cone
349 [constraints control-a control-b pivot-a pivot-b rotation]
350 (let [limit-xz (:limit-xz constraints)
351 limit-xy (:limit-xy constraints)
352 twist (:twist constraints)]
354 ;;(println-repl "creating CONE joint")
355 ;;(println-repl rotation)
356 ;;(println-repl
357 ;; "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))
358 ;;(println-repl
359 ;; "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))
360 ;;(println-repl
361 ;; "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))
362 (doto
363 (ConeJoint.
364 control-a
365 control-b
366 pivot-a
367 pivot-b
368 rotation
369 rotation)
370 (.setLimit (float limit-xz)
371 (float limit-xy)
372 (float twist)))))
374 (defn connect
375 "Create a joint between 'obj-a and 'obj-b at the location of
376 'joint. The type of joint is determined by the metadata on 'joint.
378 Here are some examples:
379 {:type :point}
380 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}
381 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)
383 {:type :cone :limit-xz 0]
384 :limit-xy 0]
385 :twist 0]} (use XZY rotation mode in blender!)"
386 [#^Node obj-a #^Node obj-b #^Node joint]
387 (let [control-a (.getControl obj-a RigidBodyControl)
388 control-b (.getControl obj-b RigidBodyControl)
389 joint-center (.getWorldTranslation joint)
390 joint-rotation (.toRotationMatrix (.getWorldRotation joint))
391 pivot-a (world-to-local obj-a joint-center)
392 pivot-b (world-to-local obj-b joint-center)]
394 (if-let [constraints
395 (map-vals
396 eval
397 (read-string
398 (meta-data joint "joint")))]
399 ;; A side-effect of creating a joint registers
400 ;; it with both physics objects which in turn
401 ;; will register the joint with the physics system
402 ;; when the simulation is started.
403 (do
404 ;;(println-repl "creating joint between"
405 ;; (.getName obj-a) "and" (.getName obj-b))
406 (joint-dispatch constraints
407 control-a control-b
408 pivot-a pivot-b
409 joint-rotation))
410 ;;(println-repl "could not find joint meta-data!")
411 )))
412 #+end_src
414 Creating joints is now a matter of applying =connect= to each joint
415 node.
417 #+name: joints-5
418 #+begin_src clojure
419 (defn joints!
420 "Connect the solid parts of the creature with physical joints. The
421 joints are taken from the \"joints\" node in the creature."
422 [#^Node creature]
423 (dorun
424 (map
425 (fn [joint]
426 (let [[obj-a obj-b] (joint-targets creature joint)]
427 (connect obj-a obj-b joint)))
428 (joints creature))))
429 #+end_src
431 ** Round 3
433 Now we can test the hand in all its glory.
435 #+name: test-3
436 #+begin_src clojure
437 (in-ns 'cortex.test.body)
439 (def debug-control
440 {"key-h" (fn [world val]
441 (if val (enable-debug world)))})
443 (defn test-hand-3
444 ([] (test-hand-3 false))
445 ([record?]
446 (world
447 (nodify
448 [(doto (hand)
449 (physical!)
450 (joints!))
451 (floor)])
452 (merge standard-debug-controls debug-control
453 gravity-control)
454 (comp
455 #(Capture/captureVideo
456 % (File. "/home/r/proj/cortex/render/body/3"))
457 #(do (set-gravity % Vector3f/ZERO) %)
458 setup)
459 no-op)))
460 #+end_src
462 =physical!= makes the hand solid, then =joints!= connects each
463 piece together.
465 #+begin_html
466 <div class="figure">
467 <center>
468 <video controls="controls" width="640">
469 <source src="../video/full-hand.ogg" type="video/ogg"
470 preload="none" poster="../images/aurellem-1280x480.png" />
471 </video>
472 <br> <a href="http://youtu.be/4affLfwSPP4"> YouTube </a>
473 </center>
474 <p>Now the hand is physical and has joints.</p>
475 </div>
476 #+end_html
478 The joints are visualized as green connections between each segment
479 for debug purposes. You can see that they correspond to the empty
480 nodes in the blender file.
482 * Wrap-Up!
484 It is convenient to combine =physical!= and =joints!= into one
485 function that completely creates the creature's physical body.
487 #+name: joints-6
488 #+begin_src clojure
489 (defn body!
490 "Endow the creature with a physical body connected with joints. The
491 particulars of the joints and the masses of each body part are
492 determined in blender."
493 [#^Node creature]
494 (physical! creature)
495 (joints! creature))
496 #+end_src
498 * The Worm
500 Going forward, I will use a model that is less complicated than the
501 hand. It has two segments and one joint, and I call it the worm. All
502 of the senses described in the following posts will be applied to this
503 worm.
505 #+name: test-4
506 #+begin_src clojure
507 (in-ns 'cortex.test.body)
509 (defn worm []
510 (load-blender-model
511 "Models/test-creature/worm.blend"))
513 (defn test-worm
515 "Testing physical bodies:
516 You should see the the worm fall onto a table. You can fire
517 physical balls at it and the worm should move upon being struck.
519 Keys:
520 <space> : fire cannon ball."
522 ([] (test-worm false))
523 ([record?]
524 (let [timer (RatchetTimer. 60)]
525 (world
526 (nodify
527 [(doto (worm)
528 (body!))
529 (floor)])
530 (merge standard-debug-controls debug-control)
531 #(do
532 (speed-up %)
533 (light-up-everything %)
534 (.setTimer % timer)
535 (cortex.util/display-dilated-time % timer)
536 (if record?
537 (Capture/captureVideo
538 % (File. "/home/r/proj/cortex/render/body/4"))))
539 no-op))))
540 #+end_src
542 #+results: test-4
543 : #'cortex.test.body/test-worm
545 #+begin_html
546 <div class="figure">
547 <center>
548 <video controls="controls" width="640">
549 <source src="../video/worm-1.ogg" type="video/ogg"
550 preload="none" poster="../images/aurellem-1280x480.png" />
551 </video>
552 <br> <a href="http://youtu.be/rFVXI0T3iSE"> YouTube </a>
553 </center>
554 <p>This worm model will be the platform onto which future senses will
555 be grafted.</p>
556 </div>
557 #+end_html
559 * Headers
560 #+name: body-header
561 #+begin_src clojure
562 (ns cortex.body
563 "Assemble a physical creature using the definitions found in a
564 specially prepared blender file. Creates rigid bodies and joints so
565 that a creature can have a physical presence in the simulation."
566 {:author "Robert McIntyre"}
567 (:use (cortex world util sense))
568 (:import
569 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)
570 (com.jme3.bullet.joints
571 SixDofJoint Point2PointJoint HingeJoint ConeJoint)
572 com.jme3.bullet.control.RigidBodyControl
573 com.jme3.collision.CollisionResults
574 com.jme3.bounding.BoundingBox
575 com.jme3.scene.Node
576 com.jme3.scene.Geometry
577 com.jme3.bullet.collision.shapes.HullCollisionShape))
578 #+end_src
580 #+name: test-header
581 #+begin_src clojure
582 (ns cortex.test.body
583 (:use (cortex world util body))
584 (:import
585 (com.aurellem.capture Capture RatchetTimer IsoTimer)
586 (com.jme3.math Quaternion Vector3f ColorRGBA)
587 java.io.File))
588 #+end_src
590 #+results: test-header
591 : java.io.File
593 * Source
594 - [[../src/cortex/body.clj][cortex.body]]
595 - [[../src/cortex/test/body.clj][cortex.test.body]]
596 - [[../assets/Models/test-creature/hand.blend][hand.blend]]
597 - [[../assets/Models/test-creature/palm.png][UV-map-1]]
598 - [[../assets/Models/test-creature/worm.blend][worm.blend]]
599 - [[../assets/Models/test-creature/retina-small.png][UV-map-1]]
600 - [[../assets/Models/test-creature/tip.png][UV-map-2]]
601 #+html: <ul> <li> <a href="../org/body.org">This org file</a> </li> </ul>
602 - [[http://hg.bortreb.com ][source-repository]]
604 * Next
605 The body I have made here exists without any senses or effectors. In
606 the [[./vision.org][next post]], I'll give the creature eyes.
608 * COMMENT Generate Source
609 #+begin_src clojure :tangle ../src/cortex/body.clj
610 <<body-header>>
611 <<body-1>>
612 <<joints-2>>
613 <<joints-3>>
614 <<joints-4>>
615 <<joints-5>>
616 <<joints-6>>
617 #+end_src
619 #+begin_src clojure :tangle ../src/cortex/test/body.clj
620 <<test-header>>
621 <<test-1>>
622 <<test-2>>
623 <<test-3>>
624 <<test-4>>
625 #+end_src