Mercurial > cortex
view org/body.org @ 284:3dd13e6095e5
discovered bug in jme's blenderLoader, saving old file.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Wed, 15 Feb 2012 16:58:33 -0700 |
parents | 23aadf376e9d |
children | 7e7f8d6d9ec5 |
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 # I'm a secret test! :P16 ** Bag of Bones18 How to create such a body? One option I ultimately rejected is to use19 blender's [[http://wiki.blender.org/index.php/Doc:2.6/Manual/Rigging/Armatures][armature]] system. The idea would have been to define a mesh20 which describes the creature's entire body. To this you add an21 skeleton which deforms this mesh. This technique is used extensively22 to model humans and create realistic animations. It is hard to use for23 my purposes because it is difficult to update the creature's Physics24 Collision Mesh in tandem with its Geometric Mesh under the influence25 of the armature. Withouth this the creature will not be able to grab26 things in its environment, and it won't be able to tell where its27 physical body is by using its eyes. Also, armatures do not specify28 any rotational limits for a joint, making it hard to model elbows,29 shoulders, etc.31 ** EVE33 Instead of using the human-like "deformable bag of bones" approach, I34 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 held40 together by invisible joint constraints. This is what I mean by41 "eve-like". The main reason that I use eve-style bodies is so that42 there will be correspondence between the AI's vision and the physical43 presence of its body. Each individual section is simulated by a44 separate rigid body that corresponds exactly with its visual45 representation and does not change. Sections are connected by46 invisible joints that are well supported in jMonkyeEngine. Bullet, the47 physics backend for jMonkeyEngine, can efficiently simulate hundreds48 of rigid bodies connected by joints. Sections do not have to stay as49 one piece forever; they can be dynamically replaced with multiple50 sections to simulate splitting in two. This could be used to simulate51 retractable claws or EVE's hands, which are able to coalece into one52 object in the movie.54 * Solidifying the Body56 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-164 #+begin_src clojure65 (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 (.setLocation73 cam (Vector3f.74 -6.9015837, 8.644911, 5.6043186))75 (.setRotation76 cam77 (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-184 ([] (test-hand-1 false))85 ([record?]86 (world (hand)87 standard-debug-controls88 (fn [world]89 (if record?90 (Capture/captureVideo91 world92 (File. "/home/r/proj/cortex/render/body/1")))93 (setup world)) no-op)))94 #+end_src97 #+begin_src clojure :results silent98 (.start (cortex.test.body/test-one))99 #+end_src101 #+begin_html102 <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 </center>109 <p>The hand model directly loaded from blender. It has no physical110 presense in the simulation. </p>111 </div>112 #+end_html114 You will notice that the hand has no physical presence -- it's a115 hologram through which everything passes. Therefore, the first thing116 to do is to make it solid. Blender has physics simulation on par with117 jMonkeyEngine (they both use bullet as their physics backend), but it118 can be difficult to translate between the two systems, so for now I119 specify the mass of each object as meta-data in blender and construct120 the physics shape based on the mesh in jMonkeyEngine.122 #+name: body-1123 #+begin_src clojure124 (defn physical!125 "Iterate through the nodes in creature and make them real physical126 objects in the simulation."127 [#^Node creature]128 (dorun129 (map130 (fn [geom]131 (let [physics-control132 (RigidBodyControl.133 (HullCollisionShape.134 (.getMesh geom))135 (if-let [mass (meta-data geom "mass")]136 (do137 (println-repl138 "setting" (.getName geom) "mass to" (float mass))139 (float mass))140 (float 1)))]141 (.addControl geom physics-control)))142 (filter #(isa? (class %) Geometry )143 (node-seq creature)))))144 #+end_src146 =physical!)= iterates through a creature's node structure, creating147 CollisionShapes for each geometry with the mass specified in that148 geometry's meta-data.150 #+name: test-2151 #+begin_src clojure152 (in-ns 'cortex.test.body)154 (def gravity-control155 {"key-g" (fn [world _]156 (set-gravity world (Vector3f. 0 -9.81 0)))157 "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-2165 ([] (test-hand-2 false))166 ([record?]167 (world168 (nodify169 [(doto (hand)170 (physical!))171 (floor)])172 (merge standard-debug-controls gravity-control)173 (fn [world]174 (if record?175 (Capture/captureVideo176 world (File. "/home/r/proj/cortex/render/body/2")))177 (set-gravity world Vector3f/ZERO)178 (setup world))179 no-op)))180 #+end_src182 #+begin_html183 <div class="figure">184 <center>185 <video controls="controls" width="640">186 <source src="../video/crumbly-hand.ogg" type="video/ogg"187 preload="none" poster="../images/aurellem-1280x480.png" />188 </video>189 </center>190 <p>The hand now has a physical presence, but there is nothing to hold191 it together.</p>192 </div>193 #+end_html195 Now that's some progress.197 * Joints199 Obviously, an AI is not going to be doing much while lying in pieces200 on the floor. So, the next step to making a proper body is to connect201 those pieces together with joints. jMonkeyEngine has a large array of202 joints available via bullet, such as Point2Point, Cone, Hinge, and a203 generic Six Degree of Freedom joint, with or without spring204 restitution.206 Although it should be possible to specify the joints using blender's207 physics system, and then automatically import them with jMonkeyEngine,208 the support isn't there yet, and there are a few problems with bullet209 itself that need to be solved before it can happen.211 So, I will use the same system for specifying joints as I will do for212 some senses. Each joint is specified by an empty node whose parent213 has the name "joints". Their orientation and meta-data determine what214 joint is created.216 #+attr_html: width="755"217 #+caption: Joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine218 [[../images/hand-screenshot1.png]]220 The empty node in the upper right, highlighted in yellow, is the221 parent node of all the emptys which represent joints. The following222 functions must do three things to translate these into real joints:224 - Find the children of the "joints" node.225 - Determine the two spatials the joint it meant to connect.226 - Create the joint based on the meta-data of the empty node.228 ** Finding the Joints230 The higher order function =sense-nodes= from =cortex.sense= simplifies231 the first task.233 #+name: joints-2234 #+begin_src clojure235 (defvar236 ^{:arglists '([creature])}237 joints238 (sense-nodes "joints")239 "Return the children of the creature's \"joints\" node.")240 #+end_src242 ** Joint Targets and Orientation244 This technique for finding a joint's targets is very similiar to245 =cortex.sense/closest-node=. A small cube, centered around the246 empty-node, grows exponentially until it intersects two /physical/247 objects. The objects are ordered according to the joint's rotation,248 with the first one being the object that has more negative coordinates249 in the joint's reference frame. Since the objects must be physical,250 the empty-node itself escapes detection. Because the objects must be251 physical, =joint-targets= must be called /after/ =physical!= is252 called.254 #+name: joints-3255 #+begin_src clojure256 (defn joint-targets257 "Return the two closest two objects to the joint object, ordered258 from bottom to top according to the joint's rotation."259 [#^Node parts #^Node joint]260 (loop [radius (float 0.01)]261 (let [results (CollisionResults.)]262 (.collideWith263 parts264 (BoundingBox. (.getWorldTranslation joint)265 radius radius radius) results)266 (let [targets267 (distinct268 (map #(.getGeometry %) results))]269 (if (>= (count targets) 2)270 (sort-by271 #(let [joint-ref-frame-position272 (jme-to-blender273 (.mult274 (.inverse (.getWorldRotation joint))275 (.subtract (.getWorldTranslation %)276 (.getWorldTranslation joint))))]277 (.dot (Vector3f. 1 1 1) joint-ref-frame-position))278 (take 2 targets))279 (recur (float (* radius 2))))))))280 #+end_src282 ** Generating Joints284 This section of code iterates through all the different ways of285 specifying joints using blender meta-data and converts each one to the286 appropriate jMonkyeEngine joint.288 #+name: joints-4289 #+begin_src clojure290 (defmulti joint-dispatch291 "Translate blender pseudo-joints into real JME joints."292 (fn [constraints & _]293 (:type constraints)))295 (defmethod joint-dispatch :point296 [constraints control-a control-b pivot-a pivot-b rotation]297 (println-repl "creating POINT2POINT joint")298 ;; bullet's point2point joints are BROKEN, so we must use the299 ;; generic 6DOF joint instead of an actual Point2Point joint!301 ;; should be able to do this:302 (comment303 (Point2PointJoint.304 control-a305 control-b306 pivot-a307 pivot-b))309 ;; but instead we must do this:310 (println-repl "substuting 6DOF joint for POINT2POINT joint!")311 (doto312 (SixDofJoint.313 control-a314 control-b315 pivot-a316 pivot-b317 false)318 (.setLinearLowerLimit Vector3f/ZERO)319 (.setLinearUpperLimit Vector3f/ZERO)))321 (defmethod joint-dispatch :hinge322 [constraints control-a control-b pivot-a pivot-b rotation]323 (println-repl "creating HINGE joint")324 (let [axis325 (if-let326 [axis (:axis constraints)]327 axis328 Vector3f/UNIT_X)329 [limit-1 limit-2] (:limit constraints)330 hinge-axis331 (.mult332 rotation333 (blender-to-jme axis))]334 (doto335 (HingeJoint.336 control-a337 control-b338 pivot-a339 pivot-b340 hinge-axis341 hinge-axis)342 (.setLimit limit-1 limit-2))))344 (defmethod joint-dispatch :cone345 [constraints control-a control-b pivot-a pivot-b rotation]346 (let [limit-xz (:limit-xz constraints)347 limit-xy (:limit-xy constraints)348 twist (:twist constraints)]350 (println-repl "creating CONE joint")351 (println-repl rotation)352 (println-repl353 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))354 (println-repl355 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))356 (println-repl357 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))358 (doto359 (ConeJoint.360 control-a361 control-b362 pivot-a363 pivot-b364 rotation365 rotation)366 (.setLimit (float limit-xz)367 (float limit-xy)368 (float twist)))))370 (defn connect371 "Create a joint between 'obj-a and 'obj-b at the location of372 'joint. The type of joint is determined by the metadata on 'joint.374 Here are some examples:375 {:type :point}376 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}377 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)379 {:type :cone :limit-xz 0]380 :limit-xy 0]381 :twist 0]} (use XZY rotation mode in blender!)"382 [#^Node obj-a #^Node obj-b #^Node joint]383 (let [control-a (.getControl obj-a RigidBodyControl)384 control-b (.getControl obj-b RigidBodyControl)385 joint-center (.getWorldTranslation joint)386 joint-rotation (.toRotationMatrix (.getWorldRotation joint))387 pivot-a (world-to-local obj-a joint-center)388 pivot-b (world-to-local obj-b joint-center)]390 (if-let [constraints391 (map-vals392 eval393 (read-string394 (meta-data joint "joint")))]395 ;; A side-effect of creating a joint registers396 ;; it with both physics objects which in turn397 ;; will register the joint with the physics system398 ;; when the simulation is started.399 (do400 (println-repl "creating joint between"401 (.getName obj-a) "and" (.getName obj-b))402 (joint-dispatch constraints403 control-a control-b404 pivot-a pivot-b405 joint-rotation))406 (println-repl "could not find joint meta-data!"))))407 #+end_src409 Creating joints is now a matter of applying =connect= to each joint410 node.412 #+name: joints-5413 #+begin_src clojure414 (defn joints!415 "Connect the solid parts of the creature with physical joints. The416 joints are taken from the \"joints\" node in the creature."417 [#^Node creature]418 (dorun419 (map420 (fn [joint]421 (let [[obj-a obj-b] (joint-targets creature joint)]422 (connect obj-a obj-b joint)))423 (joints creature))))424 #+end_src426 ** Round 3428 Now we can test the hand in all its glory.430 #+name: test-3431 #+begin_src clojure432 (in-ns 'cortex.test.body)434 (def debug-control435 {"key-h" (fn [world val]436 (if val (enable-debug world)))})438 (defn test-hand-3439 ([] (test-hand-3 false))440 ([record?]441 (world442 (nodify443 [(doto (hand)444 (physical!)445 (joints!))446 (floor)])447 (merge standard-debug-controls debug-control448 gravity-control)449 (comp450 #(Capture/captureVideo451 % (File. "/home/r/proj/cortex/render/body/3"))452 #(do (set-gravity % Vector3f/ZERO) %)453 setup)454 no-op)))455 #+end_src457 =physical!= makes the hand solid, then =joints!= connects each458 piece together.460 #+begin_html461 <div class="figure">462 <center>463 <video controls="controls" width="640">464 <source src="../video/full-hand.ogg" type="video/ogg"465 preload="none" poster="../images/aurellem-1280x480.png" />466 </video>467 </center>468 <p>Now the hand is physical and has joints.</p>469 </div>470 #+end_html472 The joints are visualized as green connections between each segment473 for debug purposes. You can see that they correspond to the empty474 nodes in the blender file.476 * Wrap-Up!478 It is convienent to combine =physical!= and =joints!= into one479 function that completely creates the creature's physical body.481 #+name: joints-6482 #+begin_src clojure483 (defn body!484 "Endow the creature with a physical body connected with joints. The485 particulars of the joints and the masses of each pody part are486 determined in blender."487 [#^Node creature]488 (physical! creature)489 (joints! creature))490 #+end_src492 * The Worm494 Going forward, I will use a model that is less complicated than the495 hand. It has two segments and one joint, and I call it the worm. All496 of the senses described in the following posts will be applied to this497 worm.499 #+name: test-4500 #+begin_src clojure501 (in-ns 'cortex.test.body)503 (defn worm []504 (load-blender-model505 "Models/test-creature/worm.blend"))507 (defn test-worm508 ([] (test-worm false))509 ([record?]510 (let [timer (RatchetTimer. 60)]511 (world512 (nodify513 [(doto (worm)514 (body!))515 (floor)])516 (merge standard-debug-controls debug-control)517 #(do518 (speed-up %)519 (light-up-everything %)520 (.setTimer % timer)521 (cortex.util/display-dialated-time % timer)522 (if record?523 (Capture/captureVideo524 % (File. "/home/r/proj/cortex/render/body/4"))))525 no-op))))526 #+end_src528 #+begin_html529 <div class="figure">530 <center>531 <video controls="controls" width="640">532 <source src="../video/worm-1.ogg" type="video/ogg"533 preload="none" poster="../images/aurellem-1280x480.png" />534 </video>535 </center>536 <p>This worm model will be the platform onto which future senses will537 be grafted.</p>538 </div>539 #+end_html541 * Headers542 #+name: body-header543 #+begin_src clojure544 (ns cortex.body545 "Assemble a physical creature using the definitions found in a546 specially prepared blender file. Creates rigid bodies and joints so547 that a creature can have a physical presense in the simulation."548 {:author "Robert McIntyre"}549 (:use (cortex world util sense))550 (:use clojure.contrib.def)551 (:import552 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)553 (com.jme3.bullet.joints554 SixDofJoint Point2PointJoint HingeJoint ConeJoint)555 com.jme3.bullet.control.RigidBodyControl556 com.jme3.collision.CollisionResults557 com.jme3.bounding.BoundingBox558 com.jme3.scene.Node559 com.jme3.scene.Geometry560 com.jme3.bullet.collision.shapes.HullCollisionShape))561 #+end_src563 #+name: test-header564 #+begin_src clojure565 (ns cortex.test.body566 (:use (cortex world util body))567 (:import568 (com.aurellem.capture Capture RatchetTimer)569 (com.jme3.math Quaternion Vector3f ColorRGBA)570 java.io.File))571 #+end_src573 * Source574 - [[../src/cortex/body.clj][cortex.body]]575 - [[../src/cortex/test/body.clj][cortex.test.body]]576 - [[../assets/Models/test-creature/hand.blend][hand.blend]]577 - [[../assets/Models/test-creature/palm.png][UV-map-1]]578 - [[../assets/Models/test-creature/worm.blend][worm.blend]]579 - [[../assets/Models/test-creature/retina-small.png][UV-map-1]]580 - [[../assets/Models/test-creature/tip.png][UV-map-2]]581 #+html: <ul> <li> <a href="../org/body.org">This org file</a> </li> </ul>582 - [[http://hg.bortreb.com ][source-repository]]584 * Next585 The body I have made here exists without any senses or effectors. In586 the [[./vision.org][next post]], I'll give the creature eyes.588 * COMMENT Generate Source589 #+begin_src clojure :tangle ../src/cortex/body.clj590 <<body-header>>591 <<body-1>>592 <<joints-2>>593 <<joints-3>>594 <<joints-4>>595 <<joints-5>>596 <<joints-6>>597 #+end_src599 #+begin_src clojure :tangle ../src/cortex/test/body.clj600 <<test-header>>601 <<test-1>>602 <<test-2>>603 <<test-3>>604 <<test-4>>605 #+end_src