view org/body.org @ 203:0e5d5ee5a914

first draft of body.org complete
author Robert McIntyre <rlm@mit.edu>
date Wed, 08 Feb 2012 08:53:12 -0700
parents d5c597a7aed4
children 162b24a82712
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, propioception) in jMonkeyEngine3.
5 #+SETUPFILE: ../../aurellem/org/setup.org
6 #+INCLUDE: ../../aurellem/org/level-0.org
9 * Design Constraints
11 I use [[www.blender.org/][blender]] to design bodies. The design of the bodies is
12 determined by the requirements of the AI that will use them. The
13 bodies must be easy for an AI to sense and control, and they must be
14 relatively simple for jMonkeyEngine to compute.
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. Withouth 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
28 any 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 The main reason that I use eve-style bodies is so that there will be
40 correspondence between the AI's vision and the physical presence of
41 its body. Each individual section is simulated by a separate rigid
42 body that corresponds exactly with its visual representation and does
43 not change. Sections are connected by invisible joints that are well
44 supported in jMonkyeEngine. Bullet, the physics backend for
45 jMonkeyEngine, can efficiently simulate hundreds of rigid bodies
46 connected by joints. Sections do not have to stay as one piece
47 forever; they can be dynamically replaced with multiple sections to
48 simulate splitting in two. This could be used to simulate retractable
49 claws or EVE's hands, which could coalece into one object in the
50 movie.
52 * Solidifying the Body
54 Here is a hand designed eve-style in blender.
56 #+attr_html: width="755"
57 [[../images/hand-screenshot0.png]]
59 If we load it directly into jMonkeyEngine, we get this:
61 #+name: test-0
62 #+begin_src clojure
63 (ns cortex.test.body
64 (:use (cortex world util body))
65 (:import (com.aurellem.capture Capture RatchetTimer)
66 (com.jme3.math Quaternion Vector3f)
67 java.io.File))
69 (def hand-path "Models/test-creature/hand.blend")
71 (defn hand [] (load-blender-model hand-path))
73 (defn setup [world]
74 (let [cam (.getCamera world)]
75 (println-repl cam)
76 (.setLocation
77 cam (Vector3f.
78 -6.9015837, 8.644911, 5.6043186))
79 (.setRotation
80 cam
81 (Quaternion.
82 0.14046453, 0.85894054, -0.34301838, 0.3533118)))
83 (light-up-everything world)
84 (.setTimer world (RatchetTimer. 60))
85 world)
87 (defn test-one []
88 (world (hand)
89 standard-debug-controls
90 (comp
91 #(Capture/captureVideo
92 % (File. "/home/r/proj/cortex/render/body/1"))
93 setup)
94 no-op))
95 #+end_src
98 #+begin_src clojure :results silent
99 (.start (cortex.test.body/test-one))
100 #+end_src
102 #+begin_html
103 <div class="figure">
104 <center>
105 <video controls="controls" width="640">
106 <source src="../video/ghost-hand.ogg" type="video/ogg"
107 preload="none" poster="../images/aurellem-1280x480.png" />
108 </video>
109 </center>
110 <p>The hand model directly loaded from blender. It has no physical
111 presense 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 witch 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 in blender and construct the physics
121 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-1
152 #+begin_src clojure
153 (in-ns 'cortex.test.body)
155 (def normal-gravity
156 {"key-g" (fn [world _]
157 (set-gravity world (Vector3f. 0 -9.81 0)))})
159 (defn floor []
160 (box 10 3 10 :position (Vector3f. 0 -10 0)
161 :color ColorRGBA/Gray :mass 0))
163 (defn test-two []
164 (world (nodify
165 [(doto (hand)
166 (physical!))
167 (floor)])
168 (merge standard-debug-controls normal-gravity)
169 (comp
170 #(Capture/captureVideo
171 % (File. "/home/r/proj/cortex/render/body/2"))
172 #(do (set-gravity % Vector3f/ZERO) %)
173 setup)
174 no-op))
175 #+end_src
177 #+begin_html
178 <div class="figure">
179 <center>
180 <video controls="controls" width="640">
181 <source src="../video/crumbly-hand.ogg" type="video/ogg"
182 preload="none" poster="../images/aurellem-1280x480.png" />
183 </video>
184 </center>
185 <p>The hand now has a physical presence, but there is nothing to hold
186 it together.</p>
187 </div>
188 #+end_html
190 Now that's some progress.
193 * Joints
195 Obviously, an AI is not going to be doing much just lying in pieces on
196 the floor. So, the next step to making a proper body is to connect
197 those pieces together with joints. jMonkeyEngine has a large array of
198 joints available via bullet, such as Point2Point, Cone, Hinge, and a
199 generic Six Degree of Freedom joint, with or without spring
200 restitution.
202 Although it should be possible to specify the joints using blender's
203 physics system, and then automatically import them with jMonkeyEngine,
204 the support isn't there yet, and there are a few problems with bullet
205 itself that need to be solved before it can happen.
207 So, I will use the same system for specifying joints as I will do for
208 some senses. Each joint is specified by an empty node whose parent
209 has the name "joints". Their orientation and meta-data determine what
210 joint is created.
212 #+attr_html: width="755"
213 #+caption: joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine
214 [[../images/hand-screenshot1.png]]
216 The empty node in the upper right, highlighted in yellow, is the
217 parent node of all the emptys which represent joints. The following
218 functions must do three things to translate these into real joints:
220 - Find the children of the "joints" node.
221 - Determine the two spatials the joint it meant to connect.
222 - Create the joint based on the meta-data of the empty node.
224 ** Finding the Joints
225 #+name: joints-2
226 #+begin_src clojure
227 (defvar
228 ^{:arglists '([creature])}
229 joints
230 (sense-nodes "joints")
231 "Return the children of the creature's \"joints\" node.")
232 #+end_src
234 The higher order function =(sense-nodes)= from cortex.sense makes our
235 first task very easy.
237 ** Joint Targets and Orientation
239 This technique for finding a joint's targets is very similiar to
240 =(cortex.sense/closest-node)=. A small cube, centered around the
241 empty-node, grows exponentially until it intersects two /physical/
242 objects. The objects are ordered according to the joint's rotation,
243 with the first one being the object that has more negative coordinates
244 in the joint's reference frame. Since the objects must be physical,
245 the empty-node itself escapes detection. Because the objects must be
246 physical, =(joint-targets)= must be called /after/ =(physical!)= is
247 called.
249 #+name: joints-3
250 #+begin_src clojure
251 (defn joint-targets
252 "Return the two closest two objects to the joint object, ordered
253 from bottom to top according to the joint's rotation."
254 [#^Node parts #^Node joint]
255 (loop [radius (float 0.01)]
256 (let [results (CollisionResults.)]
257 (.collideWith
258 parts
259 (BoundingBox. (.getWorldTranslation joint)
260 radius radius radius)
261 results)
262 (let [targets
263 (distinct
264 (map #(.getGeometry %) results))]
265 (if (>= (count targets) 2)
266 (sort-by
267 #(let [v
268 (jme-to-blender
269 (.mult
270 (.inverse (.getWorldRotation joint))
271 (.subtract (.getWorldTranslation %)
272 (.getWorldTranslation joint))))]
273 (println-repl (.getName %) ":" v)
274 (.dot (Vector3f. 1 1 1)
275 v))
276 (take 2 targets))
277 (recur (float (* radius 2))))))))
278 #+end_src
280 ** Generating Joints
282 This long chunk of code iterates through all the different ways of
283 specifying joints using blender meta-data and converts each one to the
284 appropriate jMonkyeEngine joint.
286 #+name: joints-4
287 #+begin_src clojure
288 (defmulti joint-dispatch
289 "Translate blender pseudo-joints into real JME joints."
290 (fn [constraints & _]
291 (:type constraints)))
293 (defmethod joint-dispatch :point
294 [constraints control-a control-b pivot-a pivot-b rotation]
295 (println-repl "creating POINT2POINT joint")
296 ;; bullet's point2point joints are BROKEN, so we must use the
297 ;; generic 6DOF joint instead of an actual Point2Point joint!
299 ;; should be able to do this:
300 (comment
301 (Point2PointJoint.
302 control-a
303 control-b
304 pivot-a
305 pivot-b))
307 ;; but instead we must do this:
308 (println-repl "substuting 6DOF joint for POINT2POINT joint!")
309 (doto
310 (SixDofJoint.
311 control-a
312 control-b
313 pivot-a
314 pivot-b
315 false)
316 (.setLinearLowerLimit Vector3f/ZERO)
317 (.setLinearUpperLimit Vector3f/ZERO)))
319 (defmethod joint-dispatch :hinge
320 [constraints control-a control-b pivot-a pivot-b rotation]
321 (println-repl "creating HINGE joint")
322 (let [axis
323 (if-let
324 [axis (:axis constraints)]
325 axis
326 Vector3f/UNIT_X)
327 [limit-1 limit-2] (:limit constraints)
328 hinge-axis
329 (.mult
330 rotation
331 (blender-to-jme axis))]
332 (doto
333 (HingeJoint.
334 control-a
335 control-b
336 pivot-a
337 pivot-b
338 hinge-axis
339 hinge-axis)
340 (.setLimit limit-1 limit-2))))
342 (defmethod joint-dispatch :cone
343 [constraints control-a control-b pivot-a pivot-b rotation]
344 (let [limit-xz (:limit-xz constraints)
345 limit-xy (:limit-xy constraints)
346 twist (:twist constraints)]
348 (println-repl "creating CONE joint")
349 (println-repl rotation)
350 (println-repl
351 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))
352 (println-repl
353 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))
354 (println-repl
355 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))
356 (doto
357 (ConeJoint.
358 control-a
359 control-b
360 pivot-a
361 pivot-b
362 rotation
363 rotation)
364 (.setLimit (float limit-xz)
365 (float limit-xy)
366 (float twist)))))
368 (defn connect
369 "Create a joint between 'obj-a and 'obj-b at the location of
370 'joint. The type of joint is determined by the metadata on 'joint.
372 Here are some examples:
373 {:type :point}
374 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}
375 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)
377 {:type :cone :limit-xz 0]
378 :limit-xy 0]
379 :twist 0]} (use XZY rotation mode in blender!)"
380 [#^Node obj-a #^Node obj-b #^Node joint]
381 (let [control-a (.getControl obj-a RigidBodyControl)
382 control-b (.getControl obj-b RigidBodyControl)
383 joint-center (.getWorldTranslation joint)
384 joint-rotation (.toRotationMatrix (.getWorldRotation joint))
385 pivot-a (world-to-local obj-a joint-center)
386 pivot-b (world-to-local obj-b joint-center)]
388 (if-let [constraints
389 (map-vals
390 eval
391 (read-string
392 (meta-data joint "joint")))]
393 ;; A side-effect of creating a joint registers
394 ;; it with both physics objects which in turn
395 ;; will register the joint with the physics system
396 ;; when the simulation is started.
397 (do
398 (println-repl "creating joint between"
399 (.getName obj-a) "and" (.getName obj-b))
400 (joint-dispatch constraints
401 control-a control-b
402 pivot-a pivot-b
403 joint-rotation))
404 (println-repl "could not find joint meta-data!"))))
405 #+end_src
407 Creating joints is now a matter applying =(connect)= to each joint
408 node.
410 #+begin_src clojure
411 (defn joints!
412 "Connect the solid parts of the creature with physical joints. The
413 joints are taken from the \"joints\" node in the creature."
414 [#^Node creature]
415 (dorun
416 (map
417 (fn [joint]
418 (let [[obj-a obj-b] (joint-targets creature joint)]
419 (connect obj-a obj-b joint)))
420 (joints creature))))
421 #+end_src
424 ** Round 3
426 Now we can test the hand in all its glory.
428 #+begin_src clojure
429 (in-ns 'cortex.test.body)
431 (def debug-control
432 {"key-h" (fn [world val]
433 (if val (enable-debug world)))
435 "key-u" (fn [world _] (set-gravity world Vector3f/ZERO))
436 })
438 (defn test-three []
439 (world (nodify
440 [(doto (hand)
441 (physical!)
442 (joints!) )
443 (floor)])
444 (merge standard-debug-controls debug-control
445 normal-gravity)
446 (comp
447 #(Capture/captureVideo
448 % (File. "/home/r/proj/cortex/render/body/3"))
449 #(do (set-gravity % Vector3f/ZERO) %)
450 setup)
451 no-op))
452 #+end_src
454 =(physical!)= makes the hand solid, then =(joints!)= connects each
455 piece together.
458 #+begin_html
459 <div class="figure">
460 <center>
461 <video controls="controls" width="640">
462 <source src="../video/full-hand.ogg" type="video/ogg"
463 preload="none" poster="../images/aurellem-1280x480.png" />
464 </video>
465 </center>
466 <p>Now the hand is physical and has joints.</p>
467 </div>
468 #+end_html
470 The joints are visualized as green connections between each segment
471 for debug purposes. You can see that they correspond to the empty
472 nodes in the blender file.
474 * Wrap-Up!
476 It is convienent to combine =(physical!)= and =(joints!)= into one
477 function that completely creates the creature's physical body.
479 #+name: joints-4
480 #+begin_src clojure
481 (defn body!
482 "Endow the creature with a physical body connected with joints. The
483 particulars of the joints and the masses of each pody part are
484 determined in blender."
485 [#^Node creature]
486 (physical! creature)
487 (joints! creature))
488 #+end_src
490 * Bookkeeping
492 Header; here for completeness.
494 #+name: body-0
495 #+begin_src clojure
496 (ns cortex.body
497 "Assemble a physical creature using the definitions found in a
498 specially prepared blender file. Creates rigid bodies and joints so
499 that a creature can have a physical presense in the simulation."
500 {:author "Robert McIntyre"}
501 (:use (cortex world util sense))
502 (:use clojure.contrib.def)
503 (:import
504 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)
505 (com.jme3.bullet.joints
506 SixDofJoint Point2PointJoint HingeJoint ConeJoint)
507 com.jme3.bullet.control.RigidBodyControl
508 com.jme3.collision.CollisionResults
509 com.jme3.bounding.BoundingBox
510 com.jme3.scene.Node
511 com.jme3.scene.Geometry
512 com.jme3.bullet.collision.shapes.HullCollisionShape))
513 #+end_src
515 * Source
517 Dylan -- I'll fill these in later
518 - cortex.body
519 - cortex.test.body
520 - blender files
522 * COMMENT Examples
524 #+name: test-body
525 #+begin_src clojure
526 (ns cortex.test.body
527 (:use (cortex world util body))
528 (:require cortex.silly)
529 (:import
530 com.jme3.math.Vector3f
531 com.jme3.math.ColorRGBA
532 com.jme3.bullet.joints.Point2PointJoint
533 com.jme3.bullet.control.RigidBodyControl
534 com.jme3.system.NanoTimer
535 com.jme3.math.Quaternion))
537 (defn worm-segments
538 "Create multiple evenly spaced box segments. They're fabulous!"
539 [segment-length num-segments interstitial-space radius]
540 (letfn [(nth-segment
541 [n]
542 (box segment-length radius radius :mass 0.1
543 :position
544 (Vector3f.
545 (* 2 n (+ interstitial-space segment-length)) 0 0)
546 :name (str "worm-segment" n)
547 :color (ColorRGBA/randomColor)))]
548 (map nth-segment (range num-segments))))
550 (defn connect-at-midpoint
551 "Connect two physics objects with a Point2Point joint constraint at
552 the point equidistant from both objects' centers."
553 [segmentA segmentB]
554 (let [centerA (.getWorldTranslation segmentA)
555 centerB (.getWorldTranslation segmentB)
556 midpoint (.mult (.add centerA centerB) (float 0.5))
557 pivotA (.subtract midpoint centerA)
558 pivotB (.subtract midpoint centerB)
560 ;; A side-effect of creating a joint registers
561 ;; it with both physics objects which in turn
562 ;; will register the joint with the physics system
563 ;; when the simulation is started.
564 joint (Point2PointJoint.
565 (.getControl segmentA RigidBodyControl)
566 (.getControl segmentB RigidBodyControl)
567 pivotA
568 pivotB)]
569 segmentB))
571 (defn eve-worm
572 "Create a worm-like body bound by invisible joint constraints."
573 []
574 (let [segments (worm-segments 0.2 5 0.1 0.1)]
575 (dorun (map (partial apply connect-at-midpoint)
576 (partition 2 1 segments)))
577 (nodify "worm" segments)))
579 (defn worm-pattern
580 "This is a simple, mindless motor control pattern that drives the
581 second segment of the worm's body at an offset angle with
582 sinusoidally varying strength."
583 [time]
584 (let [angle (* Math/PI (/ 9 20))
585 direction (Vector3f. 0 (Math/sin angle) (Math/cos angle))]
586 [Vector3f/ZERO
587 (.mult
588 direction
589 (float (* 2 (Math/sin (* Math/PI 2 (/ (rem time 300 ) 300))))))
590 Vector3f/ZERO
591 Vector3f/ZERO
592 Vector3f/ZERO]))
594 (defn test-motor-control
595 "Testing motor-control:
596 You should see a multi-segmented worm-like object fall onto the
597 table and begin writhing and moving."
598 []
599 (let [worm (eve-worm)
600 time (atom 0)
601 worm-motor-map (vector-motor-control worm)]
602 (world
603 (nodify [worm
604 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
605 :color ColorRGBA/Gray)])
606 standard-debug-controls
607 (fn [world]
608 (enable-debug world)
609 (light-up-everything world)
610 (comment
611 (com.aurellem.capture.Capture/captureVideo
612 world
613 (file-str "/home/r/proj/cortex/tmp/moving-worm")))
614 )
616 (fn [_ _]
617 (swap! time inc)
618 (Thread/sleep 20)
619 (dorun (worm-motor-map
620 (worm-pattern @time)))))))
624 (defn join-at-point [obj-a obj-b world-pivot]
625 (cortex.silly/joint-dispatch
626 {:type :point}
627 (.getControl obj-a RigidBodyControl)
628 (.getControl obj-b RigidBodyControl)
629 (cortex.silly/world-to-local obj-a world-pivot)
630 (cortex.silly/world-to-local obj-b world-pivot)
631 nil
632 ))
634 (import com.jme3.bullet.collision.PhysicsCollisionObject)
636 (defn blab-* []
637 (let [hand (box 0.5 0.2 0.2 :position (Vector3f. 0 0 0)
638 :mass 0 :color ColorRGBA/Green)
639 finger (box 0.5 0.2 0.2 :position (Vector3f. 2.4 0 0)
640 :mass 1 :color ColorRGBA/Red)
641 connection-point (Vector3f. 1.2 0 0)
642 root (nodify [hand finger])]
644 (join-at-point hand finger (Vector3f. 1.2 0 0))
646 (.setCollisionGroup
647 (.getControl hand RigidBodyControl)
648 PhysicsCollisionObject/COLLISION_GROUP_NONE)
649 (world
650 root
651 standard-debug-controls
652 (fn [world]
653 (enable-debug world)
654 (.setTimer world (com.aurellem.capture.RatchetTimer. 60))
655 (set-gravity world Vector3f/ZERO)
656 )
657 no-op)))
658 (comment
660 (defn proprioception-debug-window
661 []
662 (let [time (atom 0)]
663 (fn [prop-data]
664 (if (= 0 (rem (swap! time inc) 40))
665 (println-repl prop-data)))))
666 )
668 (comment
669 (dorun
670 (map
671 (comp
672 println-repl
673 (fn [[p y r]]
674 (format
675 "pitch: %1.2f\nyaw: %1.2f\nroll: %1.2f\n"
676 p y r)))
677 prop-data)))
682 (defn test-proprioception
683 "Testing proprioception:
684 You should see two foating bars, and a printout of pitch, yaw, and
685 roll. Pressing key-r/key-t should move the blue bar up and down and
686 change only the value of pitch. key-f/key-g moves it side to side
687 and changes yaw. key-v/key-b will spin the blue segment clockwise
688 and counterclockwise, and only affect roll."
689 []
690 (let [hand (box 0.2 1 0.2 :position (Vector3f. 0 0 0)
691 :mass 0 :color ColorRGBA/Green :name "hand")
692 finger (box 0.2 1 0.2 :position (Vector3f. 0 2.4 0)
693 :mass 1 :color ColorRGBA/Red :name "finger")
694 joint-node (box 0.1 0.05 0.05 :color ColorRGBA/Yellow
695 :position (Vector3f. 0 1.2 0)
696 :rotation (doto (Quaternion.)
697 (.fromAngleAxis
698 (/ Math/PI 2)
699 (Vector3f. 0 0 1)))
700 :physical? false)
701 joint (join-at-point hand finger (Vector3f. 0 1.2 0 ))
702 creature (nodify [hand finger joint-node])
703 finger-control (.getControl finger RigidBodyControl)
704 hand-control (.getControl hand RigidBodyControl)]
707 (let
708 ;; *******************************************
710 [floor (box 10 10 10 :position (Vector3f. 0 -15 0)
711 :mass 0 :color ColorRGBA/Gray)
713 root (nodify [creature floor])
714 prop (joint-proprioception creature joint-node)
715 prop-view (proprioception-debug-window)
717 controls
718 (merge standard-debug-controls
719 {"key-o"
720 (fn [_ _] (.setEnabled finger-control true))
721 "key-p"
722 (fn [_ _] (.setEnabled finger-control false))
723 "key-k"
724 (fn [_ _] (.setEnabled hand-control true))
725 "key-l"
726 (fn [_ _] (.setEnabled hand-control false))
727 "key-i"
728 (fn [world _] (set-gravity world (Vector3f. 0 0 0)))
729 "key-period"
730 (fn [world _]
731 (.setEnabled finger-control false)
732 (.setEnabled hand-control false)
733 (.rotate creature (doto (Quaternion.)
734 (.fromAngleAxis
735 (float (/ Math/PI 15))
736 (Vector3f. 0 0 -1))))
738 (.setEnabled finger-control true)
739 (.setEnabled hand-control true)
740 (set-gravity world (Vector3f. 0 0 0))
741 )
744 }
745 )
747 ]
748 (comment
749 (.setCollisionGroup
750 (.getControl hand RigidBodyControl)
751 PhysicsCollisionObject/COLLISION_GROUP_NONE)
752 )
753 (apply
754 world
755 (with-movement
756 hand
757 ["key-y" "key-u" "key-h" "key-j" "key-n" "key-m"]
758 [10 10 10 10 1 1]
759 (with-movement
760 finger
761 ["key-r" "key-t" "key-f" "key-g" "key-v" "key-b"]
762 [1 1 10 10 10 10]
763 [root
764 controls
765 (fn [world]
766 (.setTimer world (com.aurellem.capture.RatchetTimer. 60))
767 (set-gravity world (Vector3f. 0 0 0))
768 (light-up-everything world))
769 (fn [_ _] (prop-view (list (prop))))]))))))
771 #+end_src
773 #+results: test-body
774 : #'cortex.test.body/test-proprioception
777 * COMMENT code-limbo
778 #+begin_src clojure
779 ;;(.loadModel
780 ;; (doto (asset-manager)
781 ;; (.registerLoader BlenderModelLoader (into-array String ["blend"])))
782 ;; "Models/person/person.blend")
785 (defn load-blender-model
786 "Load a .blend file using an asset folder relative path."
787 [^String model]
788 (.loadModel
789 (doto (asset-manager)
790 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
791 model))
794 (defn view-model [^String model]
795 (view
796 (.loadModel
797 (doto (asset-manager)
798 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
799 model)))
801 (defn load-blender-scene [^String model]
802 (.loadModel
803 (doto (asset-manager)
804 (.registerLoader BlenderLoader (into-array String ["blend"])))
805 model))
807 (defn worm
808 []
809 (.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml"))
811 (defn oto
812 []
813 (.loadModel (asset-manager) "Models/Oto/Oto.mesh.xml"))
815 (defn sinbad
816 []
817 (.loadModel (asset-manager) "Models/Sinbad/Sinbad.mesh.xml"))
819 (defn worm-blender
820 []
821 (first (seq (.getChildren (load-blender-model
822 "Models/anim2/simple-worm.blend")))))
824 (defn body
825 "given a node with a SkeletonControl, will produce a body sutiable
826 for AI control with movement and proprioception."
827 [node]
828 (let [skeleton-control (.getControl node SkeletonControl)
829 krc (KinematicRagdollControl.)]
830 (comment
831 (dorun
832 (map #(.addBoneName krc %)
833 ["mid2" "tail" "head" "mid1" "mid3" "mid4" "Dummy-Root" ""]
834 ;;"mid2" "mid3" "tail" "head"]
835 )))
836 (.addControl node krc)
837 (.setRagdollMode krc)
838 )
839 node
840 )
841 (defn show-skeleton [node]
842 (let [sd
844 (doto
845 (SkeletonDebugger. "aurellem-skel-debug"
846 (skel node))
847 (.setMaterial (green-x-ray)))]
848 (.attachChild node sd)
849 node))
853 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
855 ;; this could be a good way to give objects special properties like
856 ;; being eyes and the like
858 (.getUserData
859 (.getChild
860 (load-blender-model "Models/property/test.blend") 0)
861 "properties")
863 ;; the properties are saved along with the blender file.
864 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
869 (defn init-debug-skel-node
870 [f debug-node skeleton]
871 (let [bones
872 (map #(.getBone skeleton %)
873 (range (.getBoneCount skeleton)))]
874 (dorun (map #(.setUserControl % true) bones))
875 (dorun (map (fn [b]
876 (println (.getName b)
877 " -- " (f b)))
878 bones))
879 (dorun
880 (map #(.attachChild
881 debug-node
882 (doto
883 (sphere 0.1
884 :position (f %)
885 :physical? false)
886 (.setMaterial (green-x-ray))))
887 bones)))
888 debug-node)
890 (import jme3test.bullet.PhysicsTestHelper)
893 (defn test-zzz [the-worm world value]
894 (if (not value)
895 (let [skeleton (skel the-worm)]
896 (println-repl "enabling bones")
897 (dorun
898 (map
899 #(.setUserControl (.getBone skeleton %) true)
900 (range (.getBoneCount skeleton))))
903 (let [b (.getBone skeleton 2)]
904 (println-repl "moving " (.getName b))
905 (println-repl (.getLocalPosition b))
906 (.setUserTransforms b
907 Vector3f/UNIT_X
908 Quaternion/IDENTITY
909 ;;(doto (Quaternion.)
910 ;; (.fromAngles (/ Math/PI 2)
911 ;; 0
912 ;; 0
914 (Vector3f. 1 1 1))
915 )
917 (println-repl "hi! <3"))))
920 (defn test-ragdoll []
922 (let [the-worm
924 ;;(.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml")
925 (doto (show-skeleton (worm-blender))
926 (.setLocalTranslation (Vector3f. 0 10 0))
927 ;;(worm)
928 ;;(oto)
929 ;;(sinbad)
930 )
931 ]
934 (.start
935 (world
936 (doto (Node.)
937 (.attachChild the-worm))
938 {"key-return" (fire-cannon-ball)
939 "key-space" (partial test-zzz the-worm)
940 }
941 (fn [world]
942 (light-up-everything world)
943 (PhysicsTestHelper/createPhysicsTestWorld
944 (.getRootNode world)
945 (asset-manager)
946 (.getPhysicsSpace
947 (.getState (.getStateManager world) BulletAppState)))
948 (set-gravity world Vector3f/ZERO)
949 ;;(.setTimer world (NanoTimer.))
950 ;;(org.lwjgl.input.Mouse/setGrabbed false)
951 )
952 no-op
953 )
956 )))
959 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
960 ;;; here is the ragdoll stuff
962 (def worm-mesh (.getMesh (.getChild (worm-blender) 0)))
963 (def mesh worm-mesh)
965 (.getFloatBuffer mesh VertexBuffer$Type/Position)
966 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)
967 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))
970 (defn position [index]
971 (.get
972 (.getFloatBuffer worm-mesh VertexBuffer$Type/Position)
973 index))
975 (defn bones [index]
976 (.get
977 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))
978 index))
980 (defn bone-weights [index]
981 (.get
982 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)
983 index))
987 (defn vertex-bones [vertex]
988 (vec (map (comp int bones) (range (* vertex 4) (+ (* vertex 4) 4)))))
990 (defn vertex-weights [vertex]
991 (vec (map (comp float bone-weights) (range (* vertex 4) (+ (* vertex 4) 4)))))
993 (defn vertex-position [index]
994 (let [offset (* index 3)]
995 (Vector3f. (position offset)
996 (position (inc offset))
997 (position (inc(inc offset))))))
999 (def vertex-info (juxt vertex-position vertex-bones vertex-weights))
1001 (defn bone-control-color [index]
1002 (get {[1 0 0 0] ColorRGBA/Red
1003 [1 2 0 0] ColorRGBA/Magenta
1004 [2 0 0 0] ColorRGBA/Blue}
1005 (vertex-bones index)
1006 ColorRGBA/White))
1008 (defn influence-color [index bone-num]
1009 (get
1010 {(float 0) ColorRGBA/Blue
1011 (float 0.5) ColorRGBA/Green
1012 (float 1) ColorRGBA/Red}
1013 ;; find the weight of the desired bone
1014 ((zipmap (vertex-bones index)(vertex-weights index))
1015 bone-num)
1016 ColorRGBA/Blue))
1018 (def worm-vertices (set (map vertex-info (range 60))))
1021 (defn test-info []
1022 (let [points (Node.)]
1023 (dorun
1024 (map #(.attachChild points %)
1025 (map #(sphere 0.01
1026 :position (vertex-position %)
1027 :color (influence-color % 1)
1028 :physical? false)
1029 (range 60))))
1030 (view points)))
1033 (defrecord JointControl [joint physics-space]
1034 PhysicsControl
1035 (setPhysicsSpace [this space]
1036 (dosync
1037 (ref-set (:physics-space this) space))
1038 (.addJoint space (:joint this)))
1039 (update [this tpf])
1040 (setSpatial [this spatial])
1041 (render [this rm vp])
1042 (getPhysicsSpace [this] (deref (:physics-space this)))
1043 (isEnabled [this] true)
1044 (setEnabled [this state]))
1046 (defn add-joint
1047 "Add a joint to a particular object. When the object is added to the
1048 PhysicsSpace of a simulation, the joint will also be added"
1049 [object joint]
1050 (let [control (JointControl. joint (ref nil))]
1051 (.addControl object control))
1052 object)
1055 (defn hinge-world
1056 []
1057 (let [sphere1 (sphere)
1058 sphere2 (sphere 1 :position (Vector3f. 3 3 3))
1059 joint (Point2PointJoint.
1060 (.getControl sphere1 RigidBodyControl)
1061 (.getControl sphere2 RigidBodyControl)
1062 Vector3f/ZERO (Vector3f. 3 3 3))]
1063 (add-joint sphere1 joint)
1064 (doto (Node. "hinge-world")
1065 (.attachChild sphere1)
1066 (.attachChild sphere2))))
1069 (defn test-joint []
1070 (view (hinge-world)))
1072 ;; (defn copier-gen []
1073 ;; (let [count (atom 0)]
1074 ;; (fn [in]
1075 ;; (swap! count inc)
1076 ;; (clojure.contrib.duck-streams/copy
1077 ;; in (File. (str "/home/r/tmp/mao-test/clojure-images/"
1078 ;; ;;/home/r/tmp/mao-test/clojure-images
1079 ;; (format "%08d.png" @count)))))))
1080 ;; (defn decrease-framerate []
1081 ;; (map
1082 ;; (copier-gen)
1083 ;; (sort
1084 ;; (map first
1085 ;; (partition
1086 ;; 4
1087 ;; (filter #(re-matches #".*.png$" (.getCanonicalPath %))
1088 ;; (file-seq
1089 ;; (file-str
1090 ;; "/home/r/media/anime/mao-temp/images"))))))))
1094 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1096 (defn proprioception
1097 "Create a proprioception map that reports the rotations of the
1098 various limbs of the creature's body"
1099 [creature]
1100 [#^Node creature]
1101 (let [
1102 nodes (node-seq creature)
1103 joints
1104 (map
1105 :joint
1106 (filter
1107 #(isa? (class %) JointControl)
1108 (reduce
1109 concat
1110 (map (fn [node]
1111 (map (fn [num] (.getControl node num))
1112 (range (.getNumControls node))))
1113 nodes))))]
1114 (fn []
1115 (reduce concat (map relative-positions (list (first joints)))))))
1118 (defn skel [node]
1119 (doto
1120 (.getSkeleton
1121 (.getControl node SkeletonControl))
1122 ;; this is necessary to force the skeleton to have accurate world
1123 ;; transforms before it is rendered to the screen.
1124 (.resetAndUpdate)))
1126 (defn green-x-ray []
1127 (doto (Material. (asset-manager)
1128 "Common/MatDefs/Misc/Unshaded.j3md")
1129 (.setColor "Color" ColorRGBA/Green)
1130 (-> (.getAdditionalRenderState)
1131 (.setDepthTest false))))
1133 (defn test-worm []
1134 (.start
1135 (world
1136 (doto (Node.)
1137 ;;(.attachChild (point-worm))
1138 (.attachChild (load-blender-model
1139 "Models/anim2/joint-worm.blend"))
1141 (.attachChild (box 10 1 10
1142 :position (Vector3f. 0 -2 0) :mass 0
1143 :color (ColorRGBA/Gray))))
1145 "key-space" (fire-cannon-ball)
1147 (fn [world]
1148 (enable-debug world)
1149 (light-up-everything world)
1150 ;;(.setTimer world (NanoTimer.))
1152 no-op)))
1156 ;; defunct movement stuff
1157 (defn torque-controls [control]
1158 (let [torques
1159 (concat
1160 (map #(Vector3f. 0 (Math/sin %) (Math/cos %))
1161 (range 0 (* Math/PI 2) (/ (* Math/PI 2) 20)))
1162 [Vector3f/UNIT_X])]
1163 (map (fn [torque-axis]
1164 (fn [torque]
1165 (.applyTorque
1166 control
1167 (.mult (.mult (.getPhysicsRotation control)
1168 torque-axis)
1169 (float
1170 (* (.getMass control) torque))))))
1171 torques)))
1173 (defn motor-map
1174 "Take a creature and generate a function that will enable fine
1175 grained control over all the creature's limbs."
1176 [#^Node creature]
1177 (let [controls (keep #(.getControl % RigidBodyControl)
1178 (node-seq creature))
1179 limb-controls (reduce concat (map torque-controls controls))
1180 body-control (partial map #(%1 %2) limb-controls)]
1181 body-control))
1183 (defn test-motor-map
1184 "see how torque works."
1185 []
1186 (let [finger (box 3 0.5 0.5 :position (Vector3f. 0 2 0)
1187 :mass 1 :color ColorRGBA/Green)
1188 motor-map (motor-map finger)]
1189 (world
1190 (nodify [finger
1191 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
1192 :color ColorRGBA/Gray)])
1193 standard-debug-controls
1194 (fn [world]
1195 (set-gravity world Vector3f/ZERO)
1196 (light-up-everything world)
1197 (.setTimer world (NanoTimer.)))
1198 (fn [_ _]
1199 (dorun (motor-map [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
1200 0]))))))
1202 (defn joint-proprioception [#^Node parts #^Node joint]
1203 (let [[obj-a obj-b] (joint-targets parts joint)
1204 joint-rot (.getWorldRotation joint)
1205 pre-inv-a (.inverse (.getWorldRotation obj-a))
1206 x (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_X))
1207 y (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_Y))
1208 z (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_Z))
1210 x Vector3f/UNIT_Y
1211 y Vector3f/UNIT_Z
1212 z Vector3f/UNIT_X
1215 tmp-rot-a (.getWorldRotation obj-a)]
1216 (println-repl "x:" (.mult tmp-rot-a x))
1217 (println-repl "y:" (.mult tmp-rot-a y))
1218 (println-repl "z:" (.mult tmp-rot-a z))
1219 (println-repl "rot-a" (.getWorldRotation obj-a))
1220 (println-repl "rot-b" (.getWorldRotation obj-b))
1221 (println-repl "joint-rot" joint-rot)
1222 ;; this function will report proprioceptive information for the
1223 ;; joint.
1224 (fn []
1225 ;; x is the "twist" axis, y and z are the "bend" axes
1226 (let [rot-a (.getWorldRotation obj-a)
1227 ;;inv-a (.inverse rot-a)
1228 rot-b (.getWorldRotation obj-b)
1229 ;;relative (.mult rot-b inv-a)
1230 basis (doto (Matrix3f.)
1231 (.setColumn 0 (.mult rot-a x))
1232 (.setColumn 1 (.mult rot-a y))
1233 (.setColumn 2 (.mult rot-a z)))
1234 rotation-about-joint
1235 (doto (Quaternion.)
1236 (.fromRotationMatrix
1237 (.mult (.invert basis)
1238 (.toRotationMatrix rot-b))))
1239 [yaw roll pitch]
1240 (seq (.toAngles rotation-about-joint nil))]
1241 ;;return euler angles of the quaternion around the new basis
1242 [yaw roll pitch]))))
1244 #+end_src
1252 * COMMENT generate Source
1253 #+begin_src clojure :tangle ../src/cortex/body.clj
1254 <<joints>>
1255 #+end_src
1257 #+begin_src clojure :tangle ../src/cortex/test/body.clj
1258 <<test-0>>
1259 #+end_src