# HG changeset patch # User Robert McIntyre # Date 1396026997 14400 # Node ID da311eefbb09e6a0b6e1ab39954627a574e15d01 # Parent e4104ce9105cfb829e1afc9bd06e200a9e2f4f9c finish body -- needs more work, but whatever. diff -r e4104ce9105c -r da311eefbb09 org/body.org --- a/org/body.org Fri Mar 28 11:08:32 2014 -0400 +++ b/org/body.org Fri Mar 28 13:16:37 2014 -0400 @@ -228,7 +228,7 @@ functions must do three things to translate these into real joints: - Find the children of the "joints" node. - - Determine the two spatials the joint it meant to connect. + - Determine the two spatials the joint is meant to connect. - Create the joint based on the meta-data of the empty node. ** Finding the Joints diff -r e4104ce9105c -r da311eefbb09 thesis/cortex.org --- a/thesis/cortex.org Fri Mar 28 11:08:32 2014 -0400 +++ b/thesis/cortex.org Fri Mar 28 13:16:37 2014 -0400 @@ -21,7 +21,7 @@ #+caption: #+name: name #+ATTR_LaTeX: :width 10cm - [[./images/Eve.jpg]] + [[./images/aurellem-gray.png]] @@ -530,16 +530,18 @@ While trying to find a good compromise for body-design, one option I ultimately rejected is to use 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 which describes the creature's - entire body. To this you add an skeleton which deforms this - mesh. This technique is used extensively to model humans and create - realistic animations. It is hard to use for my purposes because it - is difficult to update the creature's Physics Collision Mesh in - tandem with its Geometric Mesh under the influence of the - armature. Without this the creature will not be able to grab things - in its environment, and it won't be able to tell where its physical - body is by using its eyes. Also, armatures do not specify any - rotational limits for a joint, making it hard to model elbows, - shoulders, etc. + entire body. To this you add a skeleton which deforms this mesh + (called rigging). This technique is used extensively to model + humans and create realistic animations. It is not a good technique + for physical simulation, because deformable surfaces are hard to + model. Humans work like a squishy bag with some hard bones to give + it shape. The bones are easy to simulate physically, but they + interact with thr world though the skin, which is contiguous, but + does not have a constant shape. In order to simulate skin you need + some way to continuously update the physical model of the skin + along with the movement of the bones. Given that bullet is + optimized for rigid, solid objects, this leads to unmanagable + computation and incorrect simulation. Instead of using the human-like ``deformable bag of bones'' approach, I decided to base my body plans on multiple solid objects @@ -586,14 +588,248 @@ #+caption: node (highlighted in yellow) and its children which each #+caption: represent a joint in the hand. Each joint node has metadata #+caption: specifying what sort of joint it is. + #+name: blender-hand #+ATTR_LaTeX: :width 10cm [[./images/hand-screenshot1.png]] + =CORTEX= creates a creature in two steps: first, it traverses the + nodes in the blender file and creates physical representations for + any of them that have mass defined. + + #+caption: Program for iterating through the nodes in a blender file + #+caption: and generating physical jMonkeyEngine3 objects with mass + #+caption: and a matching physics shape. + #+name: name + #+begin_listing clojure + #+begin_src clojure +(defn physical! + "Iterate through the nodes in creature and make them real physical + objects in the simulation." + [#^Node creature] + (dorun + (map + (fn [geom] + (let [physics-control + (RigidBodyControl. + (HullCollisionShape. + (.getMesh geom)) + (if-let [mass (meta-data geom "mass")] + (float mass) (float 1)))] + (.addControl geom physics-control))) + (filter #(isa? (class %) Geometry ) + (node-seq creature))))) + #+end_src + #+end_listing + The next step to making a proper body is to connect those pieces + together with joints. jMonkeyEngine has a large array of joints + available via =bullet=, such as Point2Point, Cone, Hinge, and a + generic Six Degree of Freedom joint, with or without spring + restitution. =CORTEX='s procedure for binding the creature together + with joints is as follows: + - Find the children of the "joints" node. + - Determine the two spatials the joint is meant to connect. + - Create the joint based on the meta-data of the empty node. + + The higher order function =sense-nodes= from =cortex.sense= + simplifies finding the joints based on their parent ``joints'' + node. + + #+caption: Retrieving the children empty nodes from a single + #+caption: named empty node is a common pattern in =CORTEX= + #+caption: further instances of this technique for the senses + #+caption: will be omitted + #+name: get-empty-nodes + #+begin_listing clojure + #+begin_src clojure +(defn sense-nodes + "For some senses there is a special empty blender node whose + children are considered markers for an instance of that sense. This + function generates functions to find those children, given the name + of the special parent node." + [parent-name] + (fn [#^Node creature] + (if-let [sense-node (.getChild creature parent-name)] + (seq (.getChildren sense-node)) []))) + +(def + ^{:doc "Return the children of the creature's \"joints\" node." + :arglists '([creature])} + joints + (sense-nodes "joints")) + #+end_src + #+end_listing + + To find a joint's targets targets, =CORTEX= creates a small cube, + centered around the empty-node, and grows the cube exponentially + until it intersects two /physical/ objects. The objects are ordered + according to the joint's rotation, with the first one being the + object that has more negative coordinates in the joint's reference + frame. Since the objects must be physical, the empty-node itself + escapes detection. Because the objects must be physical, + =joint-targets= must be called /after/ =physical!= is called. + #+caption: Program to find the targets of a joint node by + #+caption: exponentiallly growth of a search cube. + #+name: joint-targets + #+begin_listing clojure + #+begin_src clojure +(defn joint-targets + "Return the two closest two objects to the joint object, ordered + from bottom to top according to the joint's rotation." + [#^Node parts #^Node joint] + (loop [radius (float 0.01)] + (let [results (CollisionResults.)] + (.collideWith + parts + (BoundingBox. (.getWorldTranslation joint) + radius radius radius) results) + (let [targets + (distinct + (map #(.getGeometry %) results))] + (if (>= (count targets) 2) + (sort-by + #(let [joint-ref-frame-position + (jme-to-blender + (.mult + (.inverse (.getWorldRotation joint)) + (.subtract (.getWorldTranslation %) + (.getWorldTranslation joint))))] + (.dot (Vector3f. 1 1 1) joint-ref-frame-position)) + (take 2 targets)) + (recur (float (* radius 2)))))))) + #+end_src + #+end_listing + Once =CORTEX= finds all joints and targets, it creates them using a + simple dispatch on the metadata of the joint node. + + #+caption: Program to dispatch on blender metadata and create joints + #+caption: sutiable for physical simulation. + #+name: joint-dispatch + #+begin_listing clojure + #+begin_src clojure +(defmulti joint-dispatch + "Translate blender pseudo-joints into real JME joints." + (fn [constraints & _] + (:type constraints))) + +(defmethod joint-dispatch :point + [constraints control-a control-b pivot-a pivot-b rotation] + (doto (SixDofJoint. control-a control-b pivot-a pivot-b false) + (.setLinearLowerLimit Vector3f/ZERO) + (.setLinearUpperLimit Vector3f/ZERO))) + +(defmethod joint-dispatch :hinge + [constraints control-a control-b pivot-a pivot-b rotation] + (let [axis (if-let [axis (:axis constraints)] axis Vector3f/UNIT_X) + [limit-1 limit-2] (:limit constraints) + hinge-axis (.mult rotation (blender-to-jme axis))] + (doto (HingeJoint. control-a control-b pivot-a pivot-b + hinge-axis hinge-axis) + (.setLimit limit-1 limit-2)))) + +(defmethod joint-dispatch :cone + [constraints control-a control-b pivot-a pivot-b rotation] + (let [limit-xz (:limit-xz constraints) + limit-xy (:limit-xy constraints) + twist (:twist constraints)] + (doto (ConeJoint. control-a control-b pivot-a pivot-b + rotation rotation) + (.setLimit (float limit-xz) (float limit-xy) + (float twist))))) + #+end_src + #+end_listing + + All that is left for joints it to combine the above pieces into a + something that can operate on the collection of nodes that a + blender file represents. + + #+caption: Program to completely create a joint given information + #+caption: from a blender file. + #+name: connect + #+begin_listing clojure + #+begin_src clojure +(defn connect + "Create a joint between 'obj-a and 'obj-b at the location of + 'joint. The type of joint is determined by the metadata on 'joint. + + Here are some examples: + {:type :point} + {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)} + (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints) + + {:type :cone :limit-xz 0] + :limit-xy 0] + :twist 0]} (use XZY rotation mode in blender!)" + [#^Node obj-a #^Node obj-b #^Node joint] + (let [control-a (.getControl obj-a RigidBodyControl) + control-b (.getControl obj-b RigidBodyControl) + joint-center (.getWorldTranslation joint) + joint-rotation (.toRotationMatrix (.getWorldRotation joint)) + pivot-a (world-to-local obj-a joint-center) + pivot-b (world-to-local obj-b joint-center)] + (if-let + [constraints (map-vals eval (read-string (meta-data joint "joint")))] + ;; A side-effect of creating a joint registers + ;; it with both physics objects which in turn + ;; will register the joint with the physics system + ;; when the simulation is started. + (joint-dispatch constraints + control-a control-b + pivot-a pivot-b + joint-rotation)))) + #+end_src + #+end_listing + + In general, whenever =CORTEX= exposes a sense (or in this case + physicality), it provides a function of the type =sense!=, which + takes in a collection of nodes and augments it to support that + sense. The function returns any controlls necessary to use that + sense. In this case =body!= cerates a physical body and returns no + control functions. + + #+caption: Program to give joints to a creature. + #+name: name + #+begin_listing clojure + #+begin_src clojure +(defn joints! + "Connect the solid parts of the creature with physical joints. The + joints are taken from the \"joints\" node in the creature." + [#^Node creature] + (dorun + (map + (fn [joint] + (let [[obj-a obj-b] (joint-targets creature joint)] + (connect obj-a obj-b joint))) + (joints creature)))) +(defn body! + "Endow the creature with a physical body connected with joints. The + particulars of the joints and the masses of each body part are + determined in blender." + [#^Node creature] + (physical! creature) + (joints! creature)) + #+end_src + #+end_listing + + All of the code you have just seen amounts to only 130 lines, yet + because it builds on top of Blender and jMonkeyEngine3, those few + lines pack quite a punch! + + The hand from figure \ref{blender-hand}, which was modeled after my + own right hand, can now be given joints and simulated as a + creature. + + #+caption: With the ability to create physical creatures from blender, + #+caption: =CORTEX= gets one step closer to a full creature simulation + #+caption: environment. + #+name: name + #+ATTR_LaTeX: :width 15cm + [[./images/physical-hand.png]] + ** Eyes reuse standard video game components diff -r e4104ce9105c -r da311eefbb09 thesis/images/physical-hand.png Binary file thesis/images/physical-hand.png has changed