Mercurial > cortex
view org/body.org @ 208:30bd811c5dcb
remove blender clutter from screenshots
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 09 Feb 2012 04:36:31 -0700 |
parents | bb3b75bf1664 |
children | e49c690d2bcf |
line wrap: on
line source
1 #+title: Building a Body2 #+author: Robert McIntyre3 #+email: rlm@mit.edu4 #+description: Simulating a body (movement, touch, propioception) in jMonkeyEngine3.5 #+SETUPFILE: ../../aurellem/org/setup.org6 #+INCLUDE: ../../aurellem/org/level-0.org8 * Design Constraints10 I use [[www.blender.org/][blender]] to design bodies. The design of the bodies is11 determined by the requirements of the AI that will use them. The12 bodies must be easy for an AI to sense and control, and they must be13 relatively simple for jMonkeyEngine to compute.15 ** Bag of Bones17 How to create such a body? One option I ultimately rejected is to use18 blender's [[http://wiki.blender.org/index.php/Doc:2.6/Manual/Rigging/Armatures][armature]] system. The idea would have been to define a mesh19 which describes the creature's entire body. To this you add an20 (skeleton) which deforms this mesh. This technique is used extensively21 to model humans and create realistic animations. It is hard to use for22 my purposes because it is difficult to update the creature's Physics23 Collision Mesh in tandem with its Geometric Mesh under the influence24 of the armature. Withouth this the creature will not be able to grab25 things in its environment, and it won't be able to tell where its26 physical body is by using its eyes. Also, armatures do not specify27 any rotational limits for a joint, making it hard to model elbows,28 shoulders, etc.30 ** EVE32 Instead of using the human-like "deformable bag of bones" approach, I33 decided to base my body plans on the robot EVE from the movie wall-E.35 #+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.36 [[../images/Eve.jpg]]38 EVE's body is composed of several rigid components that are held39 together by invisible joint constraints. This is what I mean by40 "eve-like". The main reason that I use eve-style bodies is so that41 there will be correspondence between the AI's vision and the physical42 presence of its body. Each individual section is simulated by a43 separate rigid body that corresponds exactly with its visual44 representation and does not change. Sections are connected by45 invisible joints that are well supported in jMonkyeEngine. Bullet, the46 physics backend for jMonkeyEngine, can efficiently simulate hundreds47 of rigid bodies connected by joints. Sections do not have to stay as48 one piece forever; they can be dynamically replaced with multiple49 sections to simulate splitting in two. This could be used to simulate50 retractable claws or EVE's hands, which could coalece into one object51 in the movie.53 * Solidifying the Body55 Here is a hand designed eve-style in blender.57 #+attr_html: width="755"58 [[../images/hand-screenshot0.png]]60 If we load it directly into jMonkeyEngine, we get this:62 #+name: test-163 #+begin_src clojure64 (def hand-path "Models/test-creature/hand.blend")66 (defn hand [] (load-blender-model hand-path))68 (defn setup [world]69 (let [cam (.getCamera world)]70 (println-repl cam)71 (.setLocation72 cam (Vector3f.73 -6.9015837, 8.644911, 5.6043186))74 (.setRotation75 cam76 (Quaternion.77 0.14046453, 0.85894054, -0.34301838, 0.3533118)))78 (light-up-everything world)79 (.setTimer world (RatchetTimer. 60))80 world)82 (defn test-one []83 (world (hand)84 standard-debug-controls85 (comp86 #(Capture/captureVideo87 % (File. "/home/r/proj/cortex/render/body/1"))88 setup)89 no-op))90 #+end_src93 #+begin_src clojure :results silent94 (.start (cortex.test.body/test-one))95 #+end_src97 #+begin_html98 <div class="figure">99 <center>100 <video controls="controls" width="640">101 <source src="../video/ghost-hand.ogg" type="video/ogg"102 preload="none" poster="../images/aurellem-1280x480.png" />103 </video>104 </center>105 <p>The hand model directly loaded from blender. It has no physical106 presense in the simulation. </p>107 </div>108 #+end_html110 You will notice that the hand has no physical presence -- it's a111 hologram through which everything passes. Therefore, the first thing112 to do is to make it solid. Blender has physics simulation on par with113 jMonkeyEngine (they both use bullet as their physics backend), but it114 can be difficult to translate between the two systems, so for now I115 specify the mass of each object in blender and construct the physics116 shape based on the mesh in jMonkeyEngine.118 #+name: body-1119 #+begin_src clojure120 (defn physical!121 "Iterate through the nodes in creature and make them real physical122 objects in the simulation."123 [#^Node creature]124 (dorun125 (map126 (fn [geom]127 (let [physics-control128 (RigidBodyControl.129 (HullCollisionShape.130 (.getMesh geom))131 (if-let [mass (meta-data geom "mass")]132 (do133 (println-repl134 "setting" (.getName geom) "mass to" (float mass))135 (float mass))136 (float 1)))]137 (.addControl geom physics-control)))138 (filter #(isa? (class %) Geometry )139 (node-seq creature)))))140 #+end_src142 =(physical!)= iterates through a creature's node structure, creating143 CollisionShapes for each geometry with the mass specified in that144 geometry's meta-data.146 #+name: test-2147 #+begin_src clojure148 (in-ns 'cortex.test.body)150 (def normal-gravity151 {"key-g" (fn [world _]152 (set-gravity world (Vector3f. 0 -9.81 0)))})154 (defn floor []155 (box 10 3 10 :position (Vector3f. 0 -10 0)156 :color ColorRGBA/Gray :mass 0))158 (defn test-two []159 (world (nodify160 [(doto (hand)161 (physical!))162 (floor)])163 (merge standard-debug-controls normal-gravity)164 (comp165 #(Capture/captureVideo166 % (File. "/home/r/proj/cortex/render/body/2"))167 #(do (set-gravity % Vector3f/ZERO) %)168 setup)169 no-op))170 #+end_src172 #+begin_html173 <div class="figure">174 <center>175 <video controls="controls" width="640">176 <source src="../video/crumbly-hand.ogg" type="video/ogg"177 preload="none" poster="../images/aurellem-1280x480.png" />178 </video>179 </center>180 <p>The hand now has a physical presence, but there is nothing to hold181 it together.</p>182 </div>183 #+end_html185 Now that's some progress.187 * Joints189 Obviously, an AI is not going to be doing much just lying in pieces on190 the floor. So, the next step to making a proper body is to connect191 those pieces together with joints. jMonkeyEngine has a large array of192 joints available via bullet, such as Point2Point, Cone, Hinge, and a193 generic Six Degree of Freedom joint, with or without spring194 restitution.196 Although it should be possible to specify the joints using blender's197 physics system, and then automatically import them with jMonkeyEngine,198 the support isn't there yet, and there are a few problems with bullet199 itself that need to be solved before it can happen.201 So, I will use the same system for specifying joints as I will do for202 some senses. Each joint is specified by an empty node whose parent203 has the name "joints". Their orientation and meta-data determine what204 joint is created.206 #+attr_html: width="755"207 #+caption: joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine208 [[../images/hand-screenshot1.png]]210 The empty node in the upper right, highlighted in yellow, is the211 parent node of all the emptys which represent joints. The following212 functions must do three things to translate these into real joints:214 - Find the children of the "joints" node.215 - Determine the two spatials the joint it meant to connect.216 - Create the joint based on the meta-data of the empty node.218 ** Finding the Joints219 #+name: joints-2220 #+begin_src clojure221 (defvar222 ^{:arglists '([creature])}223 joints224 (sense-nodes "joints")225 "Return the children of the creature's \"joints\" node.")226 #+end_src228 The higher order function =(sense-nodes)= from cortex.sense makes our229 first task very easy.231 ** Joint Targets and Orientation233 This technique for finding a joint's targets is very similiar to234 =(cortex.sense/closest-node)=. A small cube, centered around the235 empty-node, grows exponentially until it intersects two /physical/236 objects. The objects are ordered according to the joint's rotation,237 with the first one being the object that has more negative coordinates238 in the joint's reference frame. Since the objects must be physical,239 the empty-node itself escapes detection. Because the objects must be240 physical, =(joint-targets)= must be called /after/ =(physical!)= is241 called.243 #+name: joints-3244 #+begin_src clojure245 (defn joint-targets246 "Return the two closest two objects to the joint object, ordered247 from bottom to top according to the joint's rotation."248 [#^Node parts #^Node joint]249 (loop [radius (float 0.01)]250 (let [results (CollisionResults.)]251 (.collideWith252 parts253 (BoundingBox. (.getWorldTranslation joint)254 radius radius radius)255 results)256 (let [targets257 (distinct258 (map #(.getGeometry %) results))]259 (if (>= (count targets) 2)260 (sort-by261 #(let [v262 (jme-to-blender263 (.mult264 (.inverse (.getWorldRotation joint))265 (.subtract (.getWorldTranslation %)266 (.getWorldTranslation joint))))]267 (println-repl (.getName %) ":" v)268 (.dot (Vector3f. 1 1 1)269 v))270 (take 2 targets))271 (recur (float (* radius 2))))))))272 #+end_src274 ** Generating Joints276 This long chunk of code iterates through all the different ways of277 specifying joints using blender meta-data and converts each one to the278 appropriate jMonkyeEngine joint.280 #+name: joints-4281 #+begin_src clojure282 (defmulti joint-dispatch283 "Translate blender pseudo-joints into real JME joints."284 (fn [constraints & _]285 (:type constraints)))287 (defmethod joint-dispatch :point288 [constraints control-a control-b pivot-a pivot-b rotation]289 (println-repl "creating POINT2POINT joint")290 ;; bullet's point2point joints are BROKEN, so we must use the291 ;; generic 6DOF joint instead of an actual Point2Point joint!293 ;; should be able to do this:294 (comment295 (Point2PointJoint.296 control-a297 control-b298 pivot-a299 pivot-b))301 ;; but instead we must do this:302 (println-repl "substuting 6DOF joint for POINT2POINT joint!")303 (doto304 (SixDofJoint.305 control-a306 control-b307 pivot-a308 pivot-b309 false)310 (.setLinearLowerLimit Vector3f/ZERO)311 (.setLinearUpperLimit Vector3f/ZERO)))313 (defmethod joint-dispatch :hinge314 [constraints control-a control-b pivot-a pivot-b rotation]315 (println-repl "creating HINGE joint")316 (let [axis317 (if-let318 [axis (:axis constraints)]319 axis320 Vector3f/UNIT_X)321 [limit-1 limit-2] (:limit constraints)322 hinge-axis323 (.mult324 rotation325 (blender-to-jme axis))]326 (doto327 (HingeJoint.328 control-a329 control-b330 pivot-a331 pivot-b332 hinge-axis333 hinge-axis)334 (.setLimit limit-1 limit-2))))336 (defmethod joint-dispatch :cone337 [constraints control-a control-b pivot-a pivot-b rotation]338 (let [limit-xz (:limit-xz constraints)339 limit-xy (:limit-xy constraints)340 twist (:twist constraints)]342 (println-repl "creating CONE joint")343 (println-repl rotation)344 (println-repl345 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))346 (println-repl347 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))348 (println-repl349 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))350 (doto351 (ConeJoint.352 control-a353 control-b354 pivot-a355 pivot-b356 rotation357 rotation)358 (.setLimit (float limit-xz)359 (float limit-xy)360 (float twist)))))362 (defn connect363 "Create a joint between 'obj-a and 'obj-b at the location of364 'joint. The type of joint is determined by the metadata on 'joint.366 Here are some examples:367 {:type :point}368 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}369 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)371 {:type :cone :limit-xz 0]372 :limit-xy 0]373 :twist 0]} (use XZY rotation mode in blender!)"374 [#^Node obj-a #^Node obj-b #^Node joint]375 (let [control-a (.getControl obj-a RigidBodyControl)376 control-b (.getControl obj-b RigidBodyControl)377 joint-center (.getWorldTranslation joint)378 joint-rotation (.toRotationMatrix (.getWorldRotation joint))379 pivot-a (world-to-local obj-a joint-center)380 pivot-b (world-to-local obj-b joint-center)]382 (if-let [constraints383 (map-vals384 eval385 (read-string386 (meta-data joint "joint")))]387 ;; A side-effect of creating a joint registers388 ;; it with both physics objects which in turn389 ;; will register the joint with the physics system390 ;; when the simulation is started.391 (do392 (println-repl "creating joint between"393 (.getName obj-a) "and" (.getName obj-b))394 (joint-dispatch constraints395 control-a control-b396 pivot-a pivot-b397 joint-rotation))398 (println-repl "could not find joint meta-data!"))))399 #+end_src401 Creating joints is now a matter applying =(connect)= to each joint402 node.404 #+name: joints-5405 #+begin_src clojure406 (defn joints!407 "Connect the solid parts of the creature with physical joints. The408 joints are taken from the \"joints\" node in the creature."409 [#^Node creature]410 (dorun411 (map412 (fn [joint]413 (let [[obj-a obj-b] (joint-targets creature joint)]414 (connect obj-a obj-b joint)))415 (joints creature))))416 #+end_src419 ** Round 3421 Now we can test the hand in all its glory.423 #+name: test-3424 #+begin_src clojure425 (in-ns 'cortex.test.body)427 (def debug-control428 {"key-h" (fn [world val]429 (if val (enable-debug world)))430 "key-u" (fn [world _] (set-gravity world Vector3f/ZERO))})432 (defn test-three []433 (world (nodify434 [(doto (hand)435 (physical!)436 (joints!))437 (floor)])438 (merge standard-debug-controls debug-control439 normal-gravity)440 (comp441 #(Capture/captureVideo442 % (File. "/home/r/proj/cortex/render/body/3"))443 #(do (set-gravity % Vector3f/ZERO) %)444 setup)445 no-op))446 #+end_src448 =(physical!)= makes the hand solid, then =(joints!)= connects each449 piece together.451 #+begin_html452 <div class="figure">453 <center>454 <video controls="controls" width="640">455 <source src="../video/full-hand.ogg" type="video/ogg"456 preload="none" poster="../images/aurellem-1280x480.png" />457 </video>458 </center>459 <p>Now the hand is physical and has joints.</p>460 </div>461 #+end_html463 The joints are visualized as green connections between each segment464 for debug purposes. You can see that they correspond to the empty465 nodes in the blender file.467 * Wrap-Up!469 It is convienent to combine =(physical!)= and =(joints!)= into one470 function that completely creates the creature's physical body.472 #+name: joints-6473 #+begin_src clojure474 (defn body!475 "Endow the creature with a physical body connected with joints. The476 particulars of the joints and the masses of each pody part are477 determined in blender."478 [#^Node creature]479 (physical! creature)480 (joints! creature))481 #+end_src483 * The Worm485 Going forward, I will use a model that is less complicated than the486 hand. It has two segments and one joint, and I call it the worm. All487 of the senses described in the following posts will be applied to this488 worm.490 #+name: test-4491 #+begin_src clojure492 (in-ns 'cortex.test.body)494 (defn worm-1 []495 (let [timer (RatchetTimer. 60)]496 (world497 (nodify498 [(doto499 (load-blender-model500 "Models/test-creature/worm.blend")501 (body!))502 (floor)])503 (merge standard-debug-controls debug-control)504 #(do505 (speed-up %)506 (light-up-everything %)507 (.setTimer % timer)508 (cortex.util/display-dialated-time % timer)509 (Capture/captureVideo510 % (File. "/home/r/proj/cortex/render/body/4")))511 no-op)))512 #+end_src514 #+begin_html515 <div class="figure">516 <center>517 <video controls="controls" width="640">518 <source src="../video/worm-1.ogg" type="video/ogg"519 preload="none" poster="../images/aurellem-1280x480.png" />520 </video>521 </center>522 <p>This worm model will be the platform onto which future senses will523 be grafted.</p>524 </div>525 #+end_html527 * Bookkeeping529 Headers; here for completeness.531 #+name: body-header532 #+begin_src clojure533 (ns cortex.body534 "Assemble a physical creature using the definitions found in a535 specially prepared blender file. Creates rigid bodies and joints so536 that a creature can have a physical presense in the simulation."537 {:author "Robert McIntyre"}538 (:use (cortex world util sense))539 (:use clojure.contrib.def)540 (:import541 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)542 (com.jme3.bullet.joints543 SixDofJoint Point2PointJoint HingeJoint ConeJoint)544 com.jme3.bullet.control.RigidBodyControl545 com.jme3.collision.CollisionResults546 com.jme3.bounding.BoundingBox547 com.jme3.scene.Node548 com.jme3.scene.Geometry549 com.jme3.bullet.collision.shapes.HullCollisionShape))550 #+end_src552 #+name: test-header553 #+begin_src clojure554 (ns cortex.test.body555 (:use (cortex world util body))556 (:import557 (com.aurellem.capture Capture RatchetTimer)558 (com.jme3.math Quaternion Vector3f ColorRGBA)559 java.io.File))560 #+end_src562 * Source563 - [[../src/cortex/body.clj][cortex.body]]564 - [[../src/cortex/test/body.clj][cortex.test.body]]565 - [[../assets/Models/test-creature/hand.blend][hand.blend]]566 - [[../assets/Models/test-creature/worm.blend][worm.blend]]567 - [[../assets/Models/test-creature/retina-small.png][UV-map-1]]568 - [[../assets/Models/test-creature/tip.png][UV-map-2]]570 * COMMENT Generate Source571 #+begin_src clojure :tangle ../src/cortex/body.clj572 <<body-header>>573 <<body-1>>574 <<joints-2>>575 <<joints-3>>576 <<joints-4>>577 <<joints-5>>578 <<joints-6>>579 #+end_src581 #+begin_src clojure :tangle ../src/cortex/test/body.clj582 <<test-header>>583 <<test-1>>584 <<test-2>>585 <<test-3>>586 <<test-4>>587 #+end_src