view org/body.org @ 205:d3a2abfac405

fixed tangling in body.org, added dialated time HUD display in cortex.util
author Robert McIntyre <rlm@mit.edu>
date Thu, 09 Feb 2012 04:15:09 -0700
parents 162b24a82712
children df46a609fed9
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-1
64 #+begin_src clojure
65 (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 (.setLocation
73 cam (Vector3f.
74 -6.9015837, 8.644911, 5.6043186))
75 (.setRotation
76 cam
77 (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-one []
84 (world (hand)
85 standard-debug-controls
86 (comp
87 #(Capture/captureVideo
88 % (File. "/home/r/proj/cortex/render/body/1"))
89 setup)
90 no-op))
91 #+end_src
94 #+begin_src clojure :results silent
95 (.start (cortex.test.body/test-one))
96 #+end_src
98 #+begin_html
99 <div class="figure">
100 <center>
101 <video controls="controls" width="640">
102 <source src="../video/ghost-hand.ogg" type="video/ogg"
103 preload="none" poster="../images/aurellem-1280x480.png" />
104 </video>
105 </center>
106 <p>The hand model directly loaded from blender. It has no physical
107 presense in the simulation. </p>
108 </div>
109 #+end_html
111 You will notice that the hand has no physical presence -- it's a
112 hologram through which everything passes. Therefore, the first thing
113 to do is to make it solid. Blender has physics simulation on par with
114 jMonkeyEngine (they both use bullet as their physics backend), but it
115 can be difficult to translate between the two systems, so for now I
116 specify the mass of each object in blender and construct the physics
117 shape based on the mesh in jMonkeyEngine.
119 #+name: body-1
120 #+begin_src clojure
121 (defn physical!
122 "Iterate through the nodes in creature and make them real physical
123 objects in the simulation."
124 [#^Node creature]
125 (dorun
126 (map
127 (fn [geom]
128 (let [physics-control
129 (RigidBodyControl.
130 (HullCollisionShape.
131 (.getMesh geom))
132 (if-let [mass (meta-data geom "mass")]
133 (do
134 (println-repl
135 "setting" (.getName geom) "mass to" (float mass))
136 (float mass))
137 (float 1)))]
138 (.addControl geom physics-control)))
139 (filter #(isa? (class %) Geometry )
140 (node-seq creature)))))
141 #+end_src
143 =(physical!)= iterates through a creature's node structure, creating
144 CollisionShapes for each geometry with the mass specified in that
145 geometry's meta-data.
147 #+name: test-2
148 #+begin_src clojure
149 (in-ns 'cortex.test.body)
151 (def normal-gravity
152 {"key-g" (fn [world _]
153 (set-gravity world (Vector3f. 0 -9.81 0)))})
155 (defn floor []
156 (box 10 3 10 :position (Vector3f. 0 -10 0)
157 :color ColorRGBA/Gray :mass 0))
159 (defn test-two []
160 (world (nodify
161 [(doto (hand)
162 (physical!))
163 (floor)])
164 (merge standard-debug-controls normal-gravity)
165 (comp
166 #(Capture/captureVideo
167 % (File. "/home/r/proj/cortex/render/body/2"))
168 #(do (set-gravity % Vector3f/ZERO) %)
169 setup)
170 no-op))
171 #+end_src
173 #+begin_html
174 <div class="figure">
175 <center>
176 <video controls="controls" width="640">
177 <source src="../video/crumbly-hand.ogg" type="video/ogg"
178 preload="none" poster="../images/aurellem-1280x480.png" />
179 </video>
180 </center>
181 <p>The hand now has a physical presence, but there is nothing to hold
182 it together.</p>
183 </div>
184 #+end_html
186 Now that's some progress.
189 * Joints
191 Obviously, an AI is not going to be doing much just lying in pieces on
192 the floor. So, the next step to making a proper body is to connect
193 those pieces together with joints. jMonkeyEngine has a large array of
194 joints available via bullet, such as Point2Point, Cone, Hinge, and a
195 generic Six Degree of Freedom joint, with or without spring
196 restitution.
198 Although it should be possible to specify the joints using blender's
199 physics system, and then automatically import them with jMonkeyEngine,
200 the support isn't there yet, and there are a few problems with bullet
201 itself that need to be solved before it can happen.
203 So, I will use the same system for specifying joints as I will do for
204 some senses. Each joint is specified by an empty node whose parent
205 has the name "joints". Their orientation and meta-data determine what
206 joint is created.
208 #+attr_html: width="755"
209 #+caption: joints hack in blender. Each empty node here will be transformed into a joint in jMonkeyEngine
210 [[../images/hand-screenshot1.png]]
212 The empty node in the upper right, highlighted in yellow, is the
213 parent node of all the emptys which represent joints. The following
214 functions must do three things to translate these into real joints:
216 - Find the children of the "joints" node.
217 - Determine the two spatials the joint it meant to connect.
218 - Create the joint based on the meta-data of the empty node.
220 ** Finding the Joints
221 #+name: joints-2
222 #+begin_src clojure
223 (defvar
224 ^{:arglists '([creature])}
225 joints
226 (sense-nodes "joints")
227 "Return the children of the creature's \"joints\" node.")
228 #+end_src
230 The higher order function =(sense-nodes)= from cortex.sense makes our
231 first task very easy.
233 ** Joint Targets and Orientation
235 This technique for finding a joint's targets is very similiar to
236 =(cortex.sense/closest-node)=. A small cube, centered around the
237 empty-node, grows exponentially until it intersects two /physical/
238 objects. The objects are ordered according to the joint's rotation,
239 with the first one being the object that has more negative coordinates
240 in the joint's reference frame. Since the objects must be physical,
241 the empty-node itself escapes detection. Because the objects must be
242 physical, =(joint-targets)= must be called /after/ =(physical!)= is
243 called.
245 #+name: joints-3
246 #+begin_src clojure
247 (defn joint-targets
248 "Return the two closest two objects to the joint object, ordered
249 from bottom to top according to the joint's rotation."
250 [#^Node parts #^Node joint]
251 (loop [radius (float 0.01)]
252 (let [results (CollisionResults.)]
253 (.collideWith
254 parts
255 (BoundingBox. (.getWorldTranslation joint)
256 radius radius radius)
257 results)
258 (let [targets
259 (distinct
260 (map #(.getGeometry %) results))]
261 (if (>= (count targets) 2)
262 (sort-by
263 #(let [v
264 (jme-to-blender
265 (.mult
266 (.inverse (.getWorldRotation joint))
267 (.subtract (.getWorldTranslation %)
268 (.getWorldTranslation joint))))]
269 (println-repl (.getName %) ":" v)
270 (.dot (Vector3f. 1 1 1)
271 v))
272 (take 2 targets))
273 (recur (float (* radius 2))))))))
274 #+end_src
276 ** Generating Joints
278 This long chunk of code iterates through all the different ways of
279 specifying joints using blender meta-data and converts each one to the
280 appropriate jMonkyeEngine joint.
282 #+name: joints-4
283 #+begin_src clojure
284 (defmulti joint-dispatch
285 "Translate blender pseudo-joints into real JME joints."
286 (fn [constraints & _]
287 (:type constraints)))
289 (defmethod joint-dispatch :point
290 [constraints control-a control-b pivot-a pivot-b rotation]
291 (println-repl "creating POINT2POINT joint")
292 ;; bullet's point2point joints are BROKEN, so we must use the
293 ;; generic 6DOF joint instead of an actual Point2Point joint!
295 ;; should be able to do this:
296 (comment
297 (Point2PointJoint.
298 control-a
299 control-b
300 pivot-a
301 pivot-b))
303 ;; but instead we must do this:
304 (println-repl "substuting 6DOF joint for POINT2POINT joint!")
305 (doto
306 (SixDofJoint.
307 control-a
308 control-b
309 pivot-a
310 pivot-b
311 false)
312 (.setLinearLowerLimit Vector3f/ZERO)
313 (.setLinearUpperLimit Vector3f/ZERO)))
315 (defmethod joint-dispatch :hinge
316 [constraints control-a control-b pivot-a pivot-b rotation]
317 (println-repl "creating HINGE joint")
318 (let [axis
319 (if-let
320 [axis (:axis constraints)]
321 axis
322 Vector3f/UNIT_X)
323 [limit-1 limit-2] (:limit constraints)
324 hinge-axis
325 (.mult
326 rotation
327 (blender-to-jme axis))]
328 (doto
329 (HingeJoint.
330 control-a
331 control-b
332 pivot-a
333 pivot-b
334 hinge-axis
335 hinge-axis)
336 (.setLimit limit-1 limit-2))))
338 (defmethod joint-dispatch :cone
339 [constraints control-a control-b pivot-a pivot-b rotation]
340 (let [limit-xz (:limit-xz constraints)
341 limit-xy (:limit-xy constraints)
342 twist (:twist constraints)]
344 (println-repl "creating CONE joint")
345 (println-repl rotation)
346 (println-repl
347 "UNIT_X --> " (.mult rotation (Vector3f. 1 0 0)))
348 (println-repl
349 "UNIT_Y --> " (.mult rotation (Vector3f. 0 1 0)))
350 (println-repl
351 "UNIT_Z --> " (.mult rotation (Vector3f. 0 0 1)))
352 (doto
353 (ConeJoint.
354 control-a
355 control-b
356 pivot-a
357 pivot-b
358 rotation
359 rotation)
360 (.setLimit (float limit-xz)
361 (float limit-xy)
362 (float twist)))))
364 (defn connect
365 "Create a joint between 'obj-a and 'obj-b at the location of
366 'joint. The type of joint is determined by the metadata on 'joint.
368 Here are some examples:
369 {:type :point}
370 {:type :hinge :limit [0 (/ Math/PI 2)] :axis (Vector3f. 0 1 0)}
371 (:axis defaults to (Vector3f. 1 0 0) if not provided for hinge joints)
373 {:type :cone :limit-xz 0]
374 :limit-xy 0]
375 :twist 0]} (use XZY rotation mode in blender!)"
376 [#^Node obj-a #^Node obj-b #^Node joint]
377 (let [control-a (.getControl obj-a RigidBodyControl)
378 control-b (.getControl obj-b RigidBodyControl)
379 joint-center (.getWorldTranslation joint)
380 joint-rotation (.toRotationMatrix (.getWorldRotation joint))
381 pivot-a (world-to-local obj-a joint-center)
382 pivot-b (world-to-local obj-b joint-center)]
384 (if-let [constraints
385 (map-vals
386 eval
387 (read-string
388 (meta-data joint "joint")))]
389 ;; A side-effect of creating a joint registers
390 ;; it with both physics objects which in turn
391 ;; will register the joint with the physics system
392 ;; when the simulation is started.
393 (do
394 (println-repl "creating joint between"
395 (.getName obj-a) "and" (.getName obj-b))
396 (joint-dispatch constraints
397 control-a control-b
398 pivot-a pivot-b
399 joint-rotation))
400 (println-repl "could not find joint meta-data!"))))
401 #+end_src
403 Creating joints is now a matter applying =(connect)= to each joint
404 node.
406 #+name: joints-5
407 #+begin_src clojure
408 (defn joints!
409 "Connect the solid parts of the creature with physical joints. The
410 joints are taken from the \"joints\" node in the creature."
411 [#^Node creature]
412 (dorun
413 (map
414 (fn [joint]
415 (let [[obj-a obj-b] (joint-targets creature joint)]
416 (connect obj-a obj-b joint)))
417 (joints creature))))
418 #+end_src
421 ** Round 3
423 Now we can test the hand in all its glory.
425 #+name: test-3
426 #+begin_src clojure
427 (in-ns 'cortex.test.body)
429 (def debug-control
430 {"key-h" (fn [world val]
431 (if val (enable-debug world)))
432 "key-u" (fn [world _] (set-gravity world Vector3f/ZERO))})
434 (defn test-three []
435 (world (nodify
436 [(doto (hand)
437 (physical!)
438 (joints!))
439 (floor)])
440 (merge standard-debug-controls debug-control
441 normal-gravity)
442 (comp
443 #(Capture/captureVideo
444 % (File. "/home/r/proj/cortex/render/body/3"))
445 #(do (set-gravity % Vector3f/ZERO) %)
446 setup)
447 no-op))
448 #+end_src
450 =(physical!)= makes the hand solid, then =(joints!)= connects each
451 piece together.
453 #+begin_html
454 <div class="figure">
455 <center>
456 <video controls="controls" width="640">
457 <source src="../video/full-hand.ogg" type="video/ogg"
458 preload="none" poster="../images/aurellem-1280x480.png" />
459 </video>
460 </center>
461 <p>Now the hand is physical and has joints.</p>
462 </div>
463 #+end_html
465 The joints are visualized as green connections between each segment
466 for debug purposes. You can see that they correspond to the empty
467 nodes in the blender file.
469 * Wrap-Up!
471 It is convienent to combine =(physical!)= and =(joints!)= into one
472 function that completely creates the creature's physical body.
474 #+name: joints-6
475 #+begin_src clojure
476 (defn body!
477 "Endow the creature with a physical body connected with joints. The
478 particulars of the joints and the masses of each pody part are
479 determined in blender."
480 [#^Node creature]
481 (physical! creature)
482 (joints! creature))
483 #+end_src
485 * The Worm
487 Going forward, I will use a model that is less complicated than the
488 hand. It has two segments and one joint, and I call it the worm. All
489 of the senses described in the following posts will be applied to this
490 worm.
492 #+name: test-4
493 #+begin_src clojure
494 (in-ns 'cortex.test.body)
496 (defn worm-1 []
497 (let [timer (RatchetTimer. 60)]
498 (world
499 (nodify
500 [(doto
501 (load-blender-model
502 "Models/test-creature/worm.blend")
503 (body!))
504 (floor)])
505 (merge standard-debug-controls debug-control)
506 #(do
507 (speed-up %)
508 (light-up-everything %)
509 (.setTimer % timer)
510 (cortex.util/display-dialated-time % timer)
511 (Capture/captureVideo
512 % (File. "/home/r/proj/cortex/render/body/4")))
513 no-op)))
514 #+end_src
516 #+begin_html
517 <div class="figure">
518 <center>
519 <video controls="controls" width="640">
520 <source src="../video/worm-1.ogg" type="video/ogg"
521 preload="none" poster="../images/aurellem-1280x480.png" />
522 </video>
523 </center>
524 <p>This worm model will be the platform onto which future senses will
525 be grafted.</p>
526 </div>
527 #+end_html
529 * Bookkeeping
531 Header; here for completeness.
533 #+name: body-header
534 #+begin_src clojure
535 (ns cortex.body
536 "Assemble a physical creature using the definitions found in a
537 specially prepared blender file. Creates rigid bodies and joints so
538 that a creature can have a physical presense in the simulation."
539 {:author "Robert McIntyre"}
540 (:use (cortex world util sense))
541 (:use clojure.contrib.def)
542 (:import
543 (com.jme3.math Vector3f Quaternion Vector2f Matrix3f)
544 (com.jme3.bullet.joints
545 SixDofJoint Point2PointJoint HingeJoint ConeJoint)
546 com.jme3.bullet.control.RigidBodyControl
547 com.jme3.collision.CollisionResults
548 com.jme3.bounding.BoundingBox
549 com.jme3.scene.Node
550 com.jme3.scene.Geometry
551 com.jme3.bullet.collision.shapes.HullCollisionShape))
552 #+end_src
554 #+name: test-header
555 #+begin_src clojure
556 (ns cortex.test.body
557 (:use (cortex world util body))
558 (:import
559 (com.aurellem.capture Capture RatchetTimer)
560 (com.jme3.math Quaternion Vector3f ColorRGBA)
561 java.io.File))
562 #+end_src
564 * Source
566 Dylan -- I'll fill these in later
567 - cortex.body
568 - cortex.test.body
569 - blender files
571 * COMMENT Examples
573 #+name: test-body
574 #+begin_src clojure
576 (defn worm-segments
577 "Create multiple evenly spaced box segments. They're fabulous!"
578 [segment-length num-segments interstitial-space radius]
579 (letfn [(nth-segment
580 [n]
581 (box segment-length radius radius :mass 0.1
582 :position
583 (Vector3f.
584 (* 2 n (+ interstitial-space segment-length)) 0 0)
585 :name (str "worm-segment" n)
586 :color (ColorRGBA/randomColor)))]
587 (map nth-segment (range num-segments))))
589 (defn connect-at-midpoint
590 "Connect two physics objects with a Point2Point joint constraint at
591 the point equidistant from both objects' centers."
592 [segmentA segmentB]
593 (let [centerA (.getWorldTranslation segmentA)
594 centerB (.getWorldTranslation segmentB)
595 midpoint (.mult (.add centerA centerB) (float 0.5))
596 pivotA (.subtract midpoint centerA)
597 pivotB (.subtract midpoint centerB)
599 ;; A side-effect of creating a joint registers
600 ;; it with both physics objects which in turn
601 ;; will register the joint with the physics system
602 ;; when the simulation is started.
603 joint (Point2PointJoint.
604 (.getControl segmentA RigidBodyControl)
605 (.getControl segmentB RigidBodyControl)
606 pivotA
607 pivotB)]
608 segmentB))
610 (defn eve-worm
611 "Create a worm-like body bound by invisible joint constraints."
612 []
613 (let [segments (worm-segments 0.2 5 0.1 0.1)]
614 (dorun (map (partial apply connect-at-midpoint)
615 (partition 2 1 segments)))
616 (nodify "worm" segments)))
618 (defn worm-pattern
619 "This is a simple, mindless motor control pattern that drives the
620 second segment of the worm's body at an offset angle with
621 sinusoidally varying strength."
622 [time]
623 (let [angle (* Math/PI (/ 9 20))
624 direction (Vector3f. 0 (Math/sin angle) (Math/cos angle))]
625 [Vector3f/ZERO
626 (.mult
627 direction
628 (float (* 2 (Math/sin (* Math/PI 2 (/ (rem time 300 ) 300))))))
629 Vector3f/ZERO
630 Vector3f/ZERO
631 Vector3f/ZERO]))
633 (defn test-motor-control
634 "Testing motor-control:
635 You should see a multi-segmented worm-like object fall onto the
636 table and begin writhing and moving."
637 []
638 (let [worm (eve-worm)
639 time (atom 0)
640 worm-motor-map (vector-motor-control worm)]
641 (world
642 (nodify [worm
643 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
644 :color ColorRGBA/Gray)])
645 standard-debug-controls
646 (fn [world]
647 (enable-debug world)
648 (light-up-everything world)
649 (comment
650 (com.aurellem.capture.Capture/captureVideo
651 world
652 (file-str "/home/r/proj/cortex/tmp/moving-worm")))
653 )
655 (fn [_ _]
656 (swap! time inc)
657 (Thread/sleep 20)
658 (dorun (worm-motor-map
659 (worm-pattern @time)))))))
663 (defn join-at-point [obj-a obj-b world-pivot]
664 (cortex.silly/joint-dispatch
665 {:type :point}
666 (.getControl obj-a RigidBodyControl)
667 (.getControl obj-b RigidBodyControl)
668 (cortex.silly/world-to-local obj-a world-pivot)
669 (cortex.silly/world-to-local obj-b world-pivot)
670 nil
671 ))
673 (import com.jme3.bullet.collision.PhysicsCollisionObject)
675 (defn blab-* []
676 (let [hand (box 0.5 0.2 0.2 :position (Vector3f. 0 0 0)
677 :mass 0 :color ColorRGBA/Green)
678 finger (box 0.5 0.2 0.2 :position (Vector3f. 2.4 0 0)
679 :mass 1 :color ColorRGBA/Red)
680 connection-point (Vector3f. 1.2 0 0)
681 root (nodify [hand finger])]
683 (join-at-point hand finger (Vector3f. 1.2 0 0))
685 (.setCollisionGroup
686 (.getControl hand RigidBodyControl)
687 PhysicsCollisionObject/COLLISION_GROUP_NONE)
688 (world
689 root
690 standard-debug-controls
691 (fn [world]
692 (enable-debug world)
693 (.setTimer world (com.aurellem.capture.RatchetTimer. 60))
694 (set-gravity world Vector3f/ZERO)
695 )
696 no-op)))
697 (comment
699 (defn proprioception-debug-window
700 []
701 (let [time (atom 0)]
702 (fn [prop-data]
703 (if (= 0 (rem (swap! time inc) 40))
704 (println-repl prop-data)))))
705 )
707 (comment
708 (dorun
709 (map
710 (comp
711 println-repl
712 (fn [[p y r]]
713 (format
714 "pitch: %1.2f\nyaw: %1.2f\nroll: %1.2f\n"
715 p y r)))
716 prop-data)))
721 (defn test-proprioception
722 "Testing proprioception:
723 You should see two foating bars, and a printout of pitch, yaw, and
724 roll. Pressing key-r/key-t should move the blue bar up and down and
725 change only the value of pitch. key-f/key-g moves it side to side
726 and changes yaw. key-v/key-b will spin the blue segment clockwise
727 and counterclockwise, and only affect roll."
728 []
729 (let [hand (box 0.2 1 0.2 :position (Vector3f. 0 0 0)
730 :mass 0 :color ColorRGBA/Green :name "hand")
731 finger (box 0.2 1 0.2 :position (Vector3f. 0 2.4 0)
732 :mass 1 :color ColorRGBA/Red :name "finger")
733 joint-node (box 0.1 0.05 0.05 :color ColorRGBA/Yellow
734 :position (Vector3f. 0 1.2 0)
735 :rotation (doto (Quaternion.)
736 (.fromAngleAxis
737 (/ Math/PI 2)
738 (Vector3f. 0 0 1)))
739 :physical? false)
740 joint (join-at-point hand finger (Vector3f. 0 1.2 0 ))
741 creature (nodify [hand finger joint-node])
742 finger-control (.getControl finger RigidBodyControl)
743 hand-control (.getControl hand RigidBodyControl)]
746 (let
747 ;; *******************************************
749 [floor (box 10 10 10 :position (Vector3f. 0 -15 0)
750 :mass 0 :color ColorRGBA/Gray)
752 root (nodify [creature floor])
753 prop (joint-proprioception creature joint-node)
754 prop-view (proprioception-debug-window)
756 controls
757 (merge standard-debug-controls
758 {"key-o"
759 (fn [_ _] (.setEnabled finger-control true))
760 "key-p"
761 (fn [_ _] (.setEnabled finger-control false))
762 "key-k"
763 (fn [_ _] (.setEnabled hand-control true))
764 "key-l"
765 (fn [_ _] (.setEnabled hand-control false))
766 "key-i"
767 (fn [world _] (set-gravity world (Vector3f. 0 0 0)))
768 "key-period"
769 (fn [world _]
770 (.setEnabled finger-control false)
771 (.setEnabled hand-control false)
772 (.rotate creature (doto (Quaternion.)
773 (.fromAngleAxis
774 (float (/ Math/PI 15))
775 (Vector3f. 0 0 -1))))
777 (.setEnabled finger-control true)
778 (.setEnabled hand-control true)
779 (set-gravity world (Vector3f. 0 0 0))
780 )
783 }
784 )
786 ]
787 (comment
788 (.setCollisionGroup
789 (.getControl hand RigidBodyControl)
790 PhysicsCollisionObject/COLLISION_GROUP_NONE)
791 )
792 (apply
793 world
794 (with-movement
795 hand
796 ["key-y" "key-u" "key-h" "key-j" "key-n" "key-m"]
797 [10 10 10 10 1 1]
798 (with-movement
799 finger
800 ["key-r" "key-t" "key-f" "key-g" "key-v" "key-b"]
801 [1 1 10 10 10 10]
802 [root
803 controls
804 (fn [world]
805 (.setTimer world (com.aurellem.capture.RatchetTimer. 60))
806 (set-gravity world (Vector3f. 0 0 0))
807 (light-up-everything world))
808 (fn [_ _] (prop-view (list (prop))))]))))))
810 #+end_src
812 #+results: test-body
813 : #'cortex.test.body/test-proprioception
816 * COMMENT code-limbo
817 #+begin_src clojure
818 ;;(.loadModel
819 ;; (doto (asset-manager)
820 ;; (.registerLoader BlenderModelLoader (into-array String ["blend"])))
821 ;; "Models/person/person.blend")
824 (defn load-blender-model
825 "Load a .blend file using an asset folder relative path."
826 [^String model]
827 (.loadModel
828 (doto (asset-manager)
829 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
830 model))
833 (defn view-model [^String model]
834 (view
835 (.loadModel
836 (doto (asset-manager)
837 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
838 model)))
840 (defn load-blender-scene [^String model]
841 (.loadModel
842 (doto (asset-manager)
843 (.registerLoader BlenderLoader (into-array String ["blend"])))
844 model))
846 (defn worm
847 []
848 (.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml"))
850 (defn oto
851 []
852 (.loadModel (asset-manager) "Models/Oto/Oto.mesh.xml"))
854 (defn sinbad
855 []
856 (.loadModel (asset-manager) "Models/Sinbad/Sinbad.mesh.xml"))
858 (defn worm-blender
859 []
860 (first (seq (.getChildren (load-blender-model
861 "Models/anim2/simple-worm.blend")))))
863 (defn body
864 "given a node with a SkeletonControl, will produce a body sutiable
865 for AI control with movement and proprioception."
866 [node]
867 (let [skeleton-control (.getControl node SkeletonControl)
868 krc (KinematicRagdollControl.)]
869 (comment
870 (dorun
871 (map #(.addBoneName krc %)
872 ["mid2" "tail" "head" "mid1" "mid3" "mid4" "Dummy-Root" ""]
873 ;;"mid2" "mid3" "tail" "head"]
874 )))
875 (.addControl node krc)
876 (.setRagdollMode krc)
877 )
878 node
879 )
880 (defn show-skeleton [node]
881 (let [sd
883 (doto
884 (SkeletonDebugger. "aurellem-skel-debug"
885 (skel node))
886 (.setMaterial (green-x-ray)))]
887 (.attachChild node sd)
888 node))
892 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
894 ;; this could be a good way to give objects special properties like
895 ;; being eyes and the like
897 (.getUserData
898 (.getChild
899 (load-blender-model "Models/property/test.blend") 0)
900 "properties")
902 ;; the properties are saved along with the blender file.
903 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
908 (defn init-debug-skel-node
909 [f debug-node skeleton]
910 (let [bones
911 (map #(.getBone skeleton %)
912 (range (.getBoneCount skeleton)))]
913 (dorun (map #(.setUserControl % true) bones))
914 (dorun (map (fn [b]
915 (println (.getName b)
916 " -- " (f b)))
917 bones))
918 (dorun
919 (map #(.attachChild
920 debug-node
921 (doto
922 (sphere 0.1
923 :position (f %)
924 :physical? false)
925 (.setMaterial (green-x-ray))))
926 bones)))
927 debug-node)
929 (import jme3test.bullet.PhysicsTestHelper)
932 (defn test-zzz [the-worm world value]
933 (if (not value)
934 (let [skeleton (skel the-worm)]
935 (println-repl "enabling bones")
936 (dorun
937 (map
938 #(.setUserControl (.getBone skeleton %) true)
939 (range (.getBoneCount skeleton))))
942 (let [b (.getBone skeleton 2)]
943 (println-repl "moving " (.getName b))
944 (println-repl (.getLocalPosition b))
945 (.setUserTransforms b
946 Vector3f/UNIT_X
947 Quaternion/IDENTITY
948 ;;(doto (Quaternion.)
949 ;; (.fromAngles (/ Math/PI 2)
950 ;; 0
951 ;; 0
953 (Vector3f. 1 1 1))
954 )
956 (println-repl "hi! <3"))))
959 (defn test-ragdoll []
961 (let [the-worm
963 ;;(.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml")
964 (doto (show-skeleton (worm-blender))
965 (.setLocalTranslation (Vector3f. 0 10 0))
966 ;;(worm)
967 ;;(oto)
968 ;;(sinbad)
969 )
970 ]
973 (.start
974 (world
975 (doto (Node.)
976 (.attachChild the-worm))
977 {"key-return" (fire-cannon-ball)
978 "key-space" (partial test-zzz the-worm)
979 }
980 (fn [world]
981 (light-up-everything world)
982 (PhysicsTestHelper/createPhysicsTestWorld
983 (.getRootNode world)
984 (asset-manager)
985 (.getPhysicsSpace
986 (.getState (.getStateManager world) BulletAppState)))
987 (set-gravity world Vector3f/ZERO)
988 ;;(.setTimer world (NanoTimer.))
989 ;;(org.lwjgl.input.Mouse/setGrabbed false)
990 )
991 no-op
992 )
995 )))
998 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
999 ;;; here is the ragdoll stuff
1001 (def worm-mesh (.getMesh (.getChild (worm-blender) 0)))
1002 (def mesh worm-mesh)
1004 (.getFloatBuffer mesh VertexBuffer$Type/Position)
1005 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)
1006 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))
1009 (defn position [index]
1010 (.get
1011 (.getFloatBuffer worm-mesh VertexBuffer$Type/Position)
1012 index))
1014 (defn bones [index]
1015 (.get
1016 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))
1017 index))
1019 (defn bone-weights [index]
1020 (.get
1021 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)
1022 index))
1026 (defn vertex-bones [vertex]
1027 (vec (map (comp int bones) (range (* vertex 4) (+ (* vertex 4) 4)))))
1029 (defn vertex-weights [vertex]
1030 (vec (map (comp float bone-weights) (range (* vertex 4) (+ (* vertex 4) 4)))))
1032 (defn vertex-position [index]
1033 (let [offset (* index 3)]
1034 (Vector3f. (position offset)
1035 (position (inc offset))
1036 (position (inc(inc offset))))))
1038 (def vertex-info (juxt vertex-position vertex-bones vertex-weights))
1040 (defn bone-control-color [index]
1041 (get {[1 0 0 0] ColorRGBA/Red
1042 [1 2 0 0] ColorRGBA/Magenta
1043 [2 0 0 0] ColorRGBA/Blue}
1044 (vertex-bones index)
1045 ColorRGBA/White))
1047 (defn influence-color [index bone-num]
1048 (get
1049 {(float 0) ColorRGBA/Blue
1050 (float 0.5) ColorRGBA/Green
1051 (float 1) ColorRGBA/Red}
1052 ;; find the weight of the desired bone
1053 ((zipmap (vertex-bones index)(vertex-weights index))
1054 bone-num)
1055 ColorRGBA/Blue))
1057 (def worm-vertices (set (map vertex-info (range 60))))
1060 (defn test-info []
1061 (let [points (Node.)]
1062 (dorun
1063 (map #(.attachChild points %)
1064 (map #(sphere 0.01
1065 :position (vertex-position %)
1066 :color (influence-color % 1)
1067 :physical? false)
1068 (range 60))))
1069 (view points)))
1072 (defrecord JointControl [joint physics-space]
1073 PhysicsControl
1074 (setPhysicsSpace [this space]
1075 (dosync
1076 (ref-set (:physics-space this) space))
1077 (.addJoint space (:joint this)))
1078 (update [this tpf])
1079 (setSpatial [this spatial])
1080 (render [this rm vp])
1081 (getPhysicsSpace [this] (deref (:physics-space this)))
1082 (isEnabled [this] true)
1083 (setEnabled [this state]))
1085 (defn add-joint
1086 "Add a joint to a particular object. When the object is added to the
1087 PhysicsSpace of a simulation, the joint will also be added"
1088 [object joint]
1089 (let [control (JointControl. joint (ref nil))]
1090 (.addControl object control))
1091 object)
1094 (defn hinge-world
1095 []
1096 (let [sphere1 (sphere)
1097 sphere2 (sphere 1 :position (Vector3f. 3 3 3))
1098 joint (Point2PointJoint.
1099 (.getControl sphere1 RigidBodyControl)
1100 (.getControl sphere2 RigidBodyControl)
1101 Vector3f/ZERO (Vector3f. 3 3 3))]
1102 (add-joint sphere1 joint)
1103 (doto (Node. "hinge-world")
1104 (.attachChild sphere1)
1105 (.attachChild sphere2))))
1108 (defn test-joint []
1109 (view (hinge-world)))
1111 ;; (defn copier-gen []
1112 ;; (let [count (atom 0)]
1113 ;; (fn [in]
1114 ;; (swap! count inc)
1115 ;; (clojure.contrib.duck-streams/copy
1116 ;; in (File. (str "/home/r/tmp/mao-test/clojure-images/"
1117 ;; ;;/home/r/tmp/mao-test/clojure-images
1118 ;; (format "%08d.png" @count)))))))
1119 ;; (defn decrease-framerate []
1120 ;; (map
1121 ;; (copier-gen)
1122 ;; (sort
1123 ;; (map first
1124 ;; (partition
1125 ;; 4
1126 ;; (filter #(re-matches #".*.png$" (.getCanonicalPath %))
1127 ;; (file-seq
1128 ;; (file-str
1129 ;; "/home/r/media/anime/mao-temp/images"))))))))
1133 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1135 (defn proprioception
1136 "Create a proprioception map that reports the rotations of the
1137 various limbs of the creature's body"
1138 [creature]
1139 [#^Node creature]
1140 (let [
1141 nodes (node-seq creature)
1142 joints
1143 (map
1144 :joint
1145 (filter
1146 #(isa? (class %) JointControl)
1147 (reduce
1148 concat
1149 (map (fn [node]
1150 (map (fn [num] (.getControl node num))
1151 (range (.getNumControls node))))
1152 nodes))))]
1153 (fn []
1154 (reduce concat (map relative-positions (list (first joints)))))))
1157 (defn skel [node]
1158 (doto
1159 (.getSkeleton
1160 (.getControl node SkeletonControl))
1161 ;; this is necessary to force the skeleton to have accurate world
1162 ;; transforms before it is rendered to the screen.
1163 (.resetAndUpdate)))
1165 (defn green-x-ray []
1166 (doto (Material. (asset-manager)
1167 "Common/MatDefs/Misc/Unshaded.j3md")
1168 (.setColor "Color" ColorRGBA/Green)
1169 (-> (.getAdditionalRenderState)
1170 (.setDepthTest false))))
1172 (defn test-worm []
1173 (.start
1174 (world
1175 (doto (Node.)
1176 ;;(.attachChild (point-worm))
1177 (.attachChild (load-blender-model
1178 "Models/anim2/joint-worm.blend"))
1180 (.attachChild (box 10 1 10
1181 :position (Vector3f. 0 -2 0) :mass 0
1182 :color (ColorRGBA/Gray))))
1184 "key-space" (fire-cannon-ball)
1186 (fn [world]
1187 (enable-debug world)
1188 (light-up-everything world)
1189 ;;(.setTimer world (NanoTimer.))
1191 no-op)))
1195 ;; defunct movement stuff
1196 (defn torque-controls [control]
1197 (let [torques
1198 (concat
1199 (map #(Vector3f. 0 (Math/sin %) (Math/cos %))
1200 (range 0 (* Math/PI 2) (/ (* Math/PI 2) 20)))
1201 [Vector3f/UNIT_X])]
1202 (map (fn [torque-axis]
1203 (fn [torque]
1204 (.applyTorque
1205 control
1206 (.mult (.mult (.getPhysicsRotation control)
1207 torque-axis)
1208 (float
1209 (* (.getMass control) torque))))))
1210 torques)))
1212 (defn motor-map
1213 "Take a creature and generate a function that will enable fine
1214 grained control over all the creature's limbs."
1215 [#^Node creature]
1216 (let [controls (keep #(.getControl % RigidBodyControl)
1217 (node-seq creature))
1218 limb-controls (reduce concat (map torque-controls controls))
1219 body-control (partial map #(%1 %2) limb-controls)]
1220 body-control))
1222 (defn test-motor-map
1223 "see how torque works."
1224 []
1225 (let [finger (box 3 0.5 0.5 :position (Vector3f. 0 2 0)
1226 :mass 1 :color ColorRGBA/Green)
1227 motor-map (motor-map finger)]
1228 (world
1229 (nodify [finger
1230 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
1231 :color ColorRGBA/Gray)])
1232 standard-debug-controls
1233 (fn [world]
1234 (set-gravity world Vector3f/ZERO)
1235 (light-up-everything world)
1236 (.setTimer world (NanoTimer.)))
1237 (fn [_ _]
1238 (dorun (motor-map [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
1239 0]))))))
1241 (defn joint-proprioception [#^Node parts #^Node joint]
1242 (let [[obj-a obj-b] (joint-targets parts joint)
1243 joint-rot (.getWorldRotation joint)
1244 pre-inv-a (.inverse (.getWorldRotation obj-a))
1245 x (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_X))
1246 y (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_Y))
1247 z (.mult pre-inv-a (.mult joint-rot Vector3f/UNIT_Z))
1249 x Vector3f/UNIT_Y
1250 y Vector3f/UNIT_Z
1251 z Vector3f/UNIT_X
1254 tmp-rot-a (.getWorldRotation obj-a)]
1255 (println-repl "x:" (.mult tmp-rot-a x))
1256 (println-repl "y:" (.mult tmp-rot-a y))
1257 (println-repl "z:" (.mult tmp-rot-a z))
1258 (println-repl "rot-a" (.getWorldRotation obj-a))
1259 (println-repl "rot-b" (.getWorldRotation obj-b))
1260 (println-repl "joint-rot" joint-rot)
1261 ;; this function will report proprioceptive information for the
1262 ;; joint.
1263 (fn []
1264 ;; x is the "twist" axis, y and z are the "bend" axes
1265 (let [rot-a (.getWorldRotation obj-a)
1266 ;;inv-a (.inverse rot-a)
1267 rot-b (.getWorldRotation obj-b)
1268 ;;relative (.mult rot-b inv-a)
1269 basis (doto (Matrix3f.)
1270 (.setColumn 0 (.mult rot-a x))
1271 (.setColumn 1 (.mult rot-a y))
1272 (.setColumn 2 (.mult rot-a z)))
1273 rotation-about-joint
1274 (doto (Quaternion.)
1275 (.fromRotationMatrix
1276 (.mult (.invert basis)
1277 (.toRotationMatrix rot-b))))
1278 [yaw roll pitch]
1279 (seq (.toAngles rotation-about-joint nil))]
1280 ;;return euler angles of the quaternion around the new basis
1281 [yaw roll pitch]))))
1283 #+end_src
1291 * COMMENT generate Source
1292 #+begin_src clojure :tangle ../src/cortex/body.clj
1293 <<body-header>>
1294 <<body-1>>
1295 <<joints-2>>
1296 <<joints-3>>
1297 <<joints-4>>
1298 <<joints-5>>
1299 <<joints-6>>
1300 #+end_src
1302 #+begin_src clojure :tangle ../src/cortex/test/body.clj
1303 <<test-header>>
1304 <<test-1>>
1305 <<test-2>>
1306 <<test-3>>
1307 <<test-4>>
1308 #+end_src