Mercurial > cortex
view org/body.org @ 234:712bd7e5b148
saving progress
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sun, 12 Feb 2012 09:05:47 -0700 |
parents | 7bf3e3d8fb26 |
children | 63dafe7365df |
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 are able to coalece into one51 object 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 as meta-data in blender and construct116 the physics 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 gravity-control151 {"key-g" (fn [world _]152 (set-gravity world (Vector3f. 0 -9.81 0)))153 "key-u" (fn [world _] (set-gravity world Vector3f/ZERO))})156 (defn floor []157 (box 10 3 10 :position (Vector3f. 0 -10 0)158 :color ColorRGBA/Gray :mass 0))160 (defn test-two []161 (world (nodify162 [(doto (hand)163 (physical!))164 (floor)])165 (merge standard-debug-controls gravity-control)166 (comp167 #(Capture/captureVideo168 % (File. "/home/r/proj/cortex/render/body/2"))169 #(do (set-gravity % Vector3f/ZERO) %)170 setup)171 no-op))172 #+end_src174 #+begin_html175 <div class="figure">176 <center>177 <video controls="controls" width="640">178 <source src="../video/crumbly-hand.ogg" type="video/ogg"179 preload="none" poster="../images/aurellem-1280x480.png" />180 </video>181 </center>182 <p>The hand now has a physical presence, but there is nothing to hold183 it together.</p>184 </div>185 #+end_html187 Now that's some progress.189 * Joints191 Obviously, an AI is not going to be doing much while lying in pieces192 on the floor. So, the next step to making a proper body is to connect193 those pieces together with joints. jMonkeyEngine has a large array of194 joints available via bullet, such as Point2Point, Cone, Hinge, and a195 generic Six Degree of Freedom joint, with or without spring196 restitution.198 Although it should be possible to specify the joints using blender's199 physics system, and then automatically import them with jMonkeyEngine,200 the support isn't there yet, and there are a few problems with bullet201 itself that need to be solved before it can happen.203 So, I will use the same system for specifying joints as I will do for204 some senses. Each joint is specified by an empty node whose parent205 has the name "joints". Their orientation and meta-data determine what206 joint is created.208 #+attr_html: width="755"209 #+caption: Joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine210 [[../images/hand-screenshot1.png]]212 The empty node in the upper right, highlighted in yellow, is the213 parent node of all the emptys which represent joints. The following214 functions must do three things to translate these into real joints:216 - Find the children of the "joints" node.217 - Determine the two spatials the joint it meant to connect.218 - Create the joint based on the meta-data of the empty node.220 ** Finding the Joints222 The higher order function =(sense-nodes)= from =cortex.sense= simplifies223 the first task.225 #+name: joints-2226 #+begin_src clojure227 (defvar228 ^{:arglists '([creature])}229 joints230 (sense-nodes "joints")231 "Return the children of the creature's \"joints\" node.")232 #+end_src235 ** Joint Targets and Orientation237 This technique for finding a joint's targets is very similiar to238 =(cortex.sense/closest-node)=. A small cube, centered around the239 empty-node, grows exponentially until it intersects two /physical/240 objects. The objects are ordered according to the joint's rotation,241 with the first one being the object that has more negative coordinates242 in the joint's reference frame. Since the objects must be physical,243 the empty-node itself escapes detection. Because the objects must be244 physical, =(joint-targets)= must be called /after/ =(physical!)= is245 called.247 #+name: joints-3248 #+begin_src clojure249 (defn joint-targets250 "Return the two closest two objects to the joint object, ordered251 from bottom to top according to the joint's rotation."252 [#^Node parts #^Node joint]253 (loop [radius (float 0.01)]254 (let [results (CollisionResults.)]255 (.collideWith256 parts257 (BoundingBox. (.getWorldTranslation joint)258 radius radius radius) results)259 (let [targets260 (distinct261 (map #(.getGeometry %) results))]262 (if (>= (count targets) 2)263 (sort-by264 #(let [joint-ref-frame-position265 (jme-to-blender266 (.mult267 (.inverse (.getWorldRotation joint))268 (.subtract (.getWorldTranslation %)269 (.getWorldTranslation joint))))]270 (.dot (Vector3f. 1 1 1) joint-ref-frame-position))271 (take 2 targets))272 (recur (float (* radius 2))))))))273 #+end_src275 ** Generating Joints277 This section of code iterates through all the different ways of278 specifying joints using blender meta-data and converts each one to the279 appropriate jMonkyeEngine joint.281 #+name: joints-4282 #+begin_src clojure283 (defmulti joint-dispatch284 "Translate blender pseudo-joints into real JME joints."285 (fn [constraints & _]286 (:type constraints)))288 (defmethod joint-dispatch :point289 [constraints control-a control-b pivot-a pivot-b rotation]290 (println-repl "creating POINT2POINT joint")291 ;; bullet's point2point joints are BROKEN, so we must use the292 ;; generic 6DOF joint instead of an actual Point2Point joint!294 ;; should be able to do this:295 (comment296 (Point2PointJoint.297 control-a298 control-b299 pivot-a300 pivot-b))302 ;; but instead we must do this:303 (println-repl "substuting 6DOF joint for POINT2POINT joint!")304 (doto305 (SixDofJoint.306 control-a307 control-b308 pivot-a309 pivot-b310 false)311 (.setLinearLowerLimit Vector3f/ZERO)312 (.setLinearUpperLimit Vector3f/ZERO)))314 (defmethod joint-dispatch :hinge315 [constraints control-a control-b pivot-a pivot-b rotation]316 (println-repl "creating HINGE joint")317 (let [axis318 (if-let319 [axis (:axis constraints)]320 axis321 Vector3f/UNIT_X)322 [limit-1 limit-2] (:limit constraints)323 hinge-axis324 (.mult325 rotation326 (blender-to-jme axis))]327 (doto328 (HingeJoint.329 control-a330 control-b331 pivot-a332 pivot-b333 hinge-axis334 hinge-axis)335 (.setLimit limit-1 limit-2))))337 (defmethod joint-dispatch :cone338 [constraints control-a control-b pivot-a pivot-b rotation]339 (let [limit-xz (:limit-xz constraints)340 limit-xy (:limit-xy constraints)341 twist (:twist constraints)]343 (println-repl "creating CONE joint")344 (println-repl rotation)345 (println-repl346 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))347 (println-repl348 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))349 (println-repl350 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))351 (doto352 (ConeJoint.353 control-a354 control-b355 pivot-a356 pivot-b357 rotation358 rotation)359 (.setLimit (float limit-xz)360 (float limit-xy)361 (float twist)))))363 (defn connect364 "Create a joint between 'obj-a and 'obj-b at the location of365 'joint. The type of joint is determined by the metadata on 'joint.367 Here are some examples:368 {:type :point}369 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}370 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)372 {:type :cone :limit-xz 0]373 :limit-xy 0]374 :twist 0]} (use XZY rotation mode in blender!)"375 [#^Node obj-a #^Node obj-b #^Node joint]376 (let [control-a (.getControl obj-a RigidBodyControl)377 control-b (.getControl obj-b RigidBodyControl)378 joint-center (.getWorldTranslation joint)379 joint-rotation (.toRotationMatrix (.getWorldRotation joint))380 pivot-a (world-to-local obj-a joint-center)381 pivot-b (world-to-local obj-b joint-center)]383 (if-let [constraints384 (map-vals385 eval386 (read-string387 (meta-data joint "joint")))]388 ;; A side-effect of creating a joint registers389 ;; it with both physics objects which in turn390 ;; will register the joint with the physics system391 ;; when the simulation is started.392 (do393 (println-repl "creating joint between"394 (.getName obj-a) "and" (.getName obj-b))395 (joint-dispatch constraints396 control-a control-b397 pivot-a pivot-b398 joint-rotation))399 (println-repl "could not find joint meta-data!"))))400 #+end_src402 Creating joints is now a matter of applying =(connect)= to each joint403 node.405 #+name: joints-5406 #+begin_src clojure407 (defn joints!408 "Connect the solid parts of the creature with physical joints. The409 joints are taken from the \"joints\" node in the creature."410 [#^Node creature]411 (dorun412 (map413 (fn [joint]414 (let [[obj-a obj-b] (joint-targets creature joint)]415 (connect obj-a obj-b joint)))416 (joints creature))))417 #+end_src420 ** Round 3422 Now we can test the hand in all its glory.424 #+name: test-3425 #+begin_src clojure426 (in-ns 'cortex.test.body)428 (def debug-control429 {"key-h" (fn [world val]430 (if val (enable-debug world)))})432 (defn test-three []433 (world (nodify434 [(doto (hand)435 (physical!)436 (joints!))437 (floor)])438 (merge standard-debug-controls debug-control439 gravity-control)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 []495 (load-blender-model496 "Models/test-creature/worm.blend"))498 (defn worm-1 []499 (let [timer (RatchetTimer. 60)]500 (world501 (nodify502 [(doto (worm)503 (body!))504 (floor)])505 (merge standard-debug-controls debug-control)506 #(do507 (speed-up %)508 (light-up-everything %)509 (.setTimer % timer)510 (cortex.util/display-dialated-time % timer)511 (Capture/captureVideo512 % (File. "/home/r/proj/cortex/render/body/4")))513 no-op)))514 #+end_src516 #+begin_html517 <div class="figure">518 <center>519 <video controls="controls" width="640">520 <source src="../video/worm-1.ogg" type="video/ogg"521 preload="none" poster="../images/aurellem-1280x480.png" />522 </video>523 </center>524 <p>This worm model will be the platform onto which future senses will525 be grafted.</p>526 </div>527 #+end_html529 * Headers530 #+name: body-header531 #+begin_src clojure532 (ns cortex.body533 "Assemble a physical creature using the definitions found in a534 specially prepared blender file. Creates rigid bodies and joints so535 that a creature can have a physical presense in the simulation."536 {:author "Robert McIntyre"}537 (:use (cortex world util sense))538 (:use clojure.contrib.def)539 (:import540 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)541 (com.jme3.bullet.joints542 SixDofJoint Point2PointJoint HingeJoint ConeJoint)543 com.jme3.bullet.control.RigidBodyControl544 com.jme3.collision.CollisionResults545 com.jme3.bounding.BoundingBox546 com.jme3.scene.Node547 com.jme3.scene.Geometry548 com.jme3.bullet.collision.shapes.HullCollisionShape))549 #+end_src551 #+name: test-header552 #+begin_src clojure553 (ns cortex.test.body554 (:use (cortex world util body))555 (:import556 (com.aurellem.capture Capture RatchetTimer)557 (com.jme3.math Quaternion Vector3f ColorRGBA)558 java.io.File))559 #+end_src561 * Source562 - [[../src/cortex/body.clj][cortex.body]]563 - [[../src/cortex/test/body.clj][cortex.test.body]]564 - [[../assets/Models/test-creature/hand.blend][hand.blend]]565 - [[../assets/Models/test-creature/palm.png][UV-map-1]]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]]569 #+html: <ul> <li> <a href="../org/body.org">This org file</a> </li> </ul>570 - [[http://hg.bortreb.com ][source-repository]]572 * Next573 The body I have made here exists without any senses or effectors. In574 the [[./vision.org][next post]], I'll give the creature eyes.576 * COMMENT Generate Source577 #+begin_src clojure :tangle ../src/cortex/body.clj578 <<body-header>>579 <<body-1>>580 <<joints-2>>581 <<joints-3>>582 <<joints-4>>583 <<joints-5>>584 <<joints-6>>585 #+end_src587 #+begin_src clojure :tangle ../src/cortex/test/body.clj588 <<test-header>>589 <<test-1>>590 <<test-2>>591 <<test-3>>592 <<test-4>>593 #+end_src