view org/body.org @ 204:162b24a82712

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