Mercurial > cortex
view org/body.org @ 71:a1e421d9c485
improved test suite
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sun, 11 Dec 2011 22:32:28 -0700 |
parents | 39e4e1542e4a |
children | 6f1c86126d51 |
line wrap: on
line source
1 #+title: The BODY!!!2 #+author: Robert McIntyre3 #+email: rlm@mit.edu4 #+description: Simulating a body (movement, touch, propioception) in jMonkeyEngine3.5 #+SETUPFILE: ../../aurellem/org/setup.org6 #+INCLUDE: ../../aurellem/org/level-0.org10 * Proprioception11 #+name: proprioception12 #+begin_src clojure13 (ns cortex.body14 (:use (cortex world util))15 (:import16 com.jme3.math.Vector3f17 com.jme3.math.Quaternion18 com.jme3.math.Vector2f19 com.jme3.math.Matrix3f20 com.jme3.bullet.control.RigidBodyControl))22 (defn any-orthogonal23 "Generate an arbitray (but stable) orthogonal vector to a given24 vector."25 [vector]26 (let [x (.getX vector)27 y (.getY vector)28 z (.getZ vector)]29 (cond30 (not= x (float 0)) (Vector3f. (- z) 0 x)31 (not= y (float 0)) (Vector3f. 0 (- z) y)32 (not= z (float 0)) (Vector3f. 0 (- z) y)33 true Vector3f/ZERO)))35 (defn project-quaternion36 "From http://stackoverflow.com/questions/3684269/37 component-of-a-quaternion-rotation-around-an-axis.39 Determine the amount of rotation a quaternion will40 cause about a given axis."41 [#^Quaternion q #^Vector3f axis]42 (let [basis-1 (any-orthogonal axis)43 basis-2 (.cross axis basis-1)44 rotated (.mult q basis-1)45 alpha (.dot basis-1 (.project rotated basis-1))46 beta (.dot basis-2 (.project rotated basis-2))]47 (Math/atan2 beta alpha)))49 (defn joint-proprioception50 "Relative position information for a two-part system connected by a51 joint. Gives the pitch, yaw, and roll of the 'B' object relative to52 the 'A' object, as determined by the joint."53 [joint]54 (let [object-a (.getUserObject (.getBodyA joint))55 object-b (.getUserObject (.getBodyB joint))56 arm-a57 (.normalize58 (.subtract59 (.localToWorld object-a (.getPivotA joint) nil)60 (.getWorldTranslation object-a)))61 rotate-a62 (doto (Matrix3f.)63 (.fromStartEndVectors arm-a Vector3f/UNIT_X))64 arm-b65 (.mult66 rotate-a67 (.normalize68 (.subtract69 (.localToWorld object-b (.getPivotB joint) nil)70 (.getWorldTranslation object-b))))71 pitch72 (.angleBetween73 (.normalize (Vector2f. (.getX arm-b) (.getY arm-b)))74 (Vector2f. 1 0))75 yaw76 (.angleBetween77 (.normalize (Vector2f. (.getX arm-b) (.getZ arm-b)))78 (Vector2f. 1 0))80 roll81 (project-quaternion82 (.mult83 (.getLocalRotation object-b)84 (doto (Quaternion.)85 (.fromRotationMatrix rotate-a)))86 arm-b)]87 [pitch yaw roll]))89 (defn proprioception90 "Create a function that provides proprioceptive information about an91 entire body."92 [body]93 ;; extract the body's joints94 (let [joints95 (distinct96 (reduce97 concat98 (map #(.getJoints %)99 (keep100 #(.getControl % RigidBodyControl)101 (node-seq body)))))]102 (fn []103 (map joint-proprioception joints))))105 #+end_src107 * Motor Control108 #+name: motor-control109 #+begin_src clojure110 (in-ns 'cortex.body)112 ;; surprisingly enough, terristerial creatures only move by using113 ;; torque applied about their joints. There's not a single straight114 ;; line of force in the human body at all! (A straight line of force115 ;; would correspond to some sort of jet or rocket propulseion.)117 (defn vector-motor-control118 "Create a function that accepts a sequence of Vector3f objects that119 describe the torque to be applied to each part of the body."120 [body]121 (let [nodes (node-seq body)122 controls (keep #(.getControl % RigidBodyControl) nodes)]123 (fn [torques]124 (map #(.applyTorque %1 %2)125 controls torques))))126 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;127 #+end_src129 ## note -- might want to add a lower dimensional, discrete version of130 ## this if it proves useful from a x-modal clustering perspective.132 * Examples134 #+name: test-body135 #+begin_src clojure136 (ns cortex.test.body137 (:use (cortex world util body))138 (:import139 com.jme3.math.Vector3f140 com.jme3.math.ColorRGBA141 com.jme3.bullet.joints.Point2PointJoint142 com.jme3.bullet.control.RigidBodyControl143 com.jme3.system.NanoTimer))145 (defn worm-segments146 "Create multiple evenly spaced box segments. They're fabulous!"147 [segment-length num-segments interstitial-space radius]148 (letfn [(nth-segment149 [n]150 (box segment-length radius radius :mass 0.1151 :position152 (Vector3f.153 (* 2 n (+ interstitial-space segment-length)) 0 0)154 :name (str "worm-segment" n)155 :color (ColorRGBA/randomColor)))]156 (map nth-segment (range num-segments))))158 (defn connect-at-midpoint159 "Connect two physics objects with a Point2Point joint constraint at160 the point equidistant from both objects' centers."161 [segmentA segmentB]162 (let [centerA (.getWorldTranslation segmentA)163 centerB (.getWorldTranslation segmentB)164 midpoint (.mult (.add centerA centerB) (float 0.5))165 pivotA (.subtract midpoint centerA)166 pivotB (.subtract midpoint centerB)168 ;; A side-effect of creating a joint registers169 ;; it with both physics objects which in turn170 ;; will register the joint with the physics system171 ;; when the simulation is started.172 joint (Point2PointJoint.173 (.getControl segmentA RigidBodyControl)174 (.getControl segmentB RigidBodyControl)175 pivotA176 pivotB)]177 segmentB))179 (defn eve-worm180 "Create a worm body bound by invisible joint constraints."181 []182 (let [segments (worm-segments 0.2 5 0.1 0.1)]183 (dorun (map (partial apply connect-at-midpoint)184 (partition 2 1 segments)))185 (nodify "worm" segments)))187 (defn worm-pattern188 "This is a simple, mindless motor control pattern that drives the189 second segment of the worm's body at an offset angle with190 sinusoidally varying strength."191 [time]192 (let [angle (* Math/PI (/ 9 20))193 direction (Vector3f. 0 (Math/sin angle) (Math/cos angle))]194 [Vector3f/ZERO195 (.mult196 direction197 (float (* 2 (Math/sin (* Math/PI 2 (/ (rem time 300 ) 300))))))198 Vector3f/ZERO199 Vector3f/ZERO200 Vector3f/ZERO]))202 (defn test-motor-control203 "Testing motor-control:204 You should see a multi-segmented worm-like object fall onto the205 table and begin writhing and moving."206 []207 (let [worm (eve-worm)208 time (atom 0)209 worm-motor-map (vector-motor-control worm)]210 (world211 (nodify [worm212 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0213 :color ColorRGBA/Gray)])214 standard-debug-controls215 (fn [world]216 (enable-debug world)217 (light-up-everything world)218 (comment219 (com.aurellem.capture.Capture/captureVideo220 world221 (file-str "/home/r/proj/cortex/tmp/moving-worm")))222 )224 (fn [_ _]225 (swap! time inc)226 (Thread/sleep 20)227 (dorun (worm-motor-map228 (worm-pattern @time)))))))230 (defn test-proprioception231 "Testing proprioception:232 You should see two foating bars, and a printout of pitch, yaw, and233 roll. Pressing key-r/key-t should move the blue bar up and down and234 change only the value of pitch. key-f/key-g moves it side to side235 and changes yaw. key-v/key-b will spin the blue segment clockwise236 and counterclockwise, and only affect roll."237 []238 (let [hand (box 1 0.2 0.2 :position (Vector3f. 0 2 0)239 :mass 0 :color ColorRGBA/Green)240 finger (box 1 0.2 0.2 :position (Vector3f. 2.4 2 0)241 :mass 1 :color (ColorRGBA. 0.20 0.40 0.99 1.0))242 floor (box 10 0.5 10 :position (Vector3f. 0 -5 0)243 :mass 0 :color ColorRGBA/Gray)245 move-up? (atom false)246 move-down? (atom false)247 move-left? (atom false)248 move-right? (atom false)249 roll-left? (atom false)250 roll-right? (atom false)251 control (.getControl finger RigidBodyControl)252 joint253 (doto254 (Point2PointJoint.255 (.getControl hand RigidBodyControl)256 control257 (Vector3f. 1.2 0 0)258 (Vector3f. -1.2 0 0 ))259 (.setCollisionBetweenLinkedBodys false))260 time (atom 0)]261 (world262 (nodify [hand finger floor])263 (merge standard-debug-controls264 {"key-r" (fn [_ pressed?] (reset! move-up? pressed?))265 "key-t" (fn [_ pressed?] (reset! move-down? pressed?))266 "key-f" (fn [_ pressed?] (reset! move-left? pressed?))267 "key-g" (fn [_ pressed?] (reset! move-right? pressed?))268 "key-v" (fn [_ pressed?] (reset! roll-left? pressed?))269 "key-b" (fn [_ pressed?] (reset! roll-right? pressed?))})270 (fn [world]271 (.setTimer world (NanoTimer.))272 (set-gravity world (Vector3f. 0 0 0))273 (.setMoveSpeed (.getFlyByCamera world) 50)274 (.setRotationSpeed (.getFlyByCamera world) 50)275 (light-up-everything world))276 (fn [_ _]277 (if @move-up?278 (.applyTorque control279 (.mult (.getPhysicsRotation control)280 (Vector3f. 0 0 10))))281 (if @move-down?282 (.applyTorque control283 (.mult (.getPhysicsRotation control)284 (Vector3f. 0 0 -10))))285 (if @move-left?286 (.applyTorque control287 (.mult (.getPhysicsRotation control)288 (Vector3f. 0 10 0))))289 (if @move-right?290 (.applyTorque control291 (.mult (.getPhysicsRotation control)292 (Vector3f. 0 -10 0))))293 (if @roll-left?294 (.applyTorque control295 (.mult (.getPhysicsRotation control)296 (Vector3f. -1 0 0))))297 (if @roll-right?298 (.applyTorque control299 (.mult (.getPhysicsRotation control)300 (Vector3f. 1 0 0))))302 (if (= 0 (rem (swap! time inc) 2000))303 (do304 (apply305 (comp306 println-repl307 #(format "pitch: %1.2f\nyaw: %1.2f\nroll: %1.2f\n" %1 %2 %3))308 (joint-proprioception joint))))))))309 #+end_src311 #+results: test-body312 : #'test.body/test-proprioception316 * COMMENT code-limbo317 #+begin_src clojure318 ;;(.loadModel319 ;; (doto (asset-manager)320 ;; (.registerLoader BlenderModelLoader (into-array String ["blend"])))321 ;; "Models/person/person.blend")324 (defn load-blender-model325 "Load a .blend file using an asset folder relative path."326 [^String model]327 (.loadModel328 (doto (asset-manager)329 (.registerLoader BlenderModelLoader (into-array String ["blend"])))330 model))333 (defn view-model [^String model]334 (view335 (.loadModel336 (doto (asset-manager)337 (.registerLoader BlenderModelLoader (into-array String ["blend"])))338 model)))340 (defn load-blender-scene [^String model]341 (.loadModel342 (doto (asset-manager)343 (.registerLoader BlenderLoader (into-array String ["blend"])))344 model))346 (defn worm347 []348 (.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml"))350 (defn oto351 []352 (.loadModel (asset-manager) "Models/Oto/Oto.mesh.xml"))354 (defn sinbad355 []356 (.loadModel (asset-manager) "Models/Sinbad/Sinbad.mesh.xml"))358 (defn worm-blender359 []360 (first (seq (.getChildren (load-blender-model361 "Models/anim2/simple-worm.blend")))))363 (defn body364 "given a node with a SkeletonControl, will produce a body sutiable365 for AI control with movement and proprioception."366 [node]367 (let [skeleton-control (.getControl node SkeletonControl)368 krc (KinematicRagdollControl.)]369 (comment370 (dorun371 (map #(.addBoneName krc %)372 ["mid2" "tail" "head" "mid1" "mid3" "mid4" "Dummy-Root" ""]373 ;;"mid2" "mid3" "tail" "head"]374 )))375 (.addControl node krc)376 (.setRagdollMode krc)377 )378 node379 )380 (defn show-skeleton [node]381 (let [sd383 (doto384 (SkeletonDebugger. "aurellem-skel-debug"385 (skel node))386 (.setMaterial (green-x-ray)))]387 (.attachChild node sd)388 node))392 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;394 ;; this could be a good way to give objects special properties like395 ;; being eyes and the like397 (.getUserData398 (.getChild399 (load-blender-model "Models/property/test.blend") 0)400 "properties")402 ;; the properties are saved along with the blender file.403 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;408 (defn init-debug-skel-node409 [f debug-node skeleton]410 (let [bones411 (map #(.getBone skeleton %)412 (range (.getBoneCount skeleton)))]413 (dorun (map #(.setUserControl % true) bones))414 (dorun (map (fn [b]415 (println (.getName b)416 " -- " (f b)))417 bones))418 (dorun419 (map #(.attachChild420 debug-node421 (doto422 (sphere 0.1423 :position (f %)424 :physical? false)425 (.setMaterial (green-x-ray))))426 bones)))427 debug-node)429 (import jme3test.bullet.PhysicsTestHelper)432 (defn test-zzz [the-worm world value]433 (if (not value)434 (let [skeleton (skel the-worm)]435 (println-repl "enabling bones")436 (dorun437 (map438 #(.setUserControl (.getBone skeleton %) true)439 (range (.getBoneCount skeleton))))442 (let [b (.getBone skeleton 2)]443 (println-repl "moving " (.getName b))444 (println-repl (.getLocalPosition b))445 (.setUserTransforms b446 Vector3f/UNIT_X447 Quaternion/IDENTITY448 ;;(doto (Quaternion.)449 ;; (.fromAngles (/ Math/PI 2)450 ;; 0451 ;; 0453 (Vector3f. 1 1 1))454 )456 (println-repl "hi! <3"))))459 (defn test-ragdoll []461 (let [the-worm463 ;;(.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml")464 (doto (show-skeleton (worm-blender))465 (.setLocalTranslation (Vector3f. 0 10 0))466 ;;(worm)467 ;;(oto)468 ;;(sinbad)469 )470 ]473 (.start474 (world475 (doto (Node.)476 (.attachChild the-worm))477 {"key-return" (fire-cannon-ball)478 "key-space" (partial test-zzz the-worm)479 }480 (fn [world]481 (light-up-everything world)482 (PhysicsTestHelper/createPhysicsTestWorld483 (.getRootNode world)484 (asset-manager)485 (.getPhysicsSpace486 (.getState (.getStateManager world) BulletAppState)))487 (set-gravity world Vector3f/ZERO)488 ;;(.setTimer world (NanoTimer.))489 ;;(org.lwjgl.input.Mouse/setGrabbed false)490 )491 no-op492 )495 )))498 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;499 ;;; here is the ragdoll stuff501 (def worm-mesh (.getMesh (.getChild (worm-blender) 0)))502 (def mesh worm-mesh)504 (.getFloatBuffer mesh VertexBuffer$Type/Position)505 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)506 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))509 (defn position [index]510 (.get511 (.getFloatBuffer worm-mesh VertexBuffer$Type/Position)512 index))514 (defn bones [index]515 (.get516 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))517 index))519 (defn bone-weights [index]520 (.get521 (.getFloatBuffer mesh VertexBuffer$Type/BoneWeight)522 index))526 (defn vertex-bones [vertex]527 (vec (map (comp int bones) (range (* vertex 4) (+ (* vertex 4) 4)))))529 (defn vertex-weights [vertex]530 (vec (map (comp float bone-weights) (range (* vertex 4) (+ (* vertex 4) 4)))))532 (defn vertex-position [index]533 (let [offset (* index 3)]534 (Vector3f. (position offset)535 (position (inc offset))536 (position (inc(inc offset))))))538 (def vertex-info (juxt vertex-position vertex-bones vertex-weights))540 (defn bone-control-color [index]541 (get {[1 0 0 0] ColorRGBA/Red542 [1 2 0 0] ColorRGBA/Magenta543 [2 0 0 0] ColorRGBA/Blue}544 (vertex-bones index)545 ColorRGBA/White))547 (defn influence-color [index bone-num]548 (get549 {(float 0) ColorRGBA/Blue550 (float 0.5) ColorRGBA/Green551 (float 1) ColorRGBA/Red}552 ;; find the weight of the desired bone553 ((zipmap (vertex-bones index)(vertex-weights index))554 bone-num)555 ColorRGBA/Blue))557 (def worm-vertices (set (map vertex-info (range 60))))560 (defn test-info []561 (let [points (Node.)]562 (dorun563 (map #(.attachChild points %)564 (map #(sphere 0.01565 :position (vertex-position %)566 :color (influence-color % 1)567 :physical? false)568 (range 60))))569 (view points)))572 (defrecord JointControl [joint physics-space]573 PhysicsControl574 (setPhysicsSpace [this space]575 (dosync576 (ref-set (:physics-space this) space))577 (.addJoint space (:joint this)))578 (update [this tpf])579 (setSpatial [this spatial])580 (render [this rm vp])581 (getPhysicsSpace [this] (deref (:physics-space this)))582 (isEnabled [this] true)583 (setEnabled [this state]))585 (defn add-joint586 "Add a joint to a particular object. When the object is added to the587 PhysicsSpace of a simulation, the joint will also be added"588 [object joint]589 (let [control (JointControl. joint (ref nil))]590 (.addControl object control))591 object)594 (defn hinge-world595 []596 (let [sphere1 (sphere)597 sphere2 (sphere 1 :position (Vector3f. 3 3 3))598 joint (Point2PointJoint.599 (.getControl sphere1 RigidBodyControl)600 (.getControl sphere2 RigidBodyControl)601 Vector3f/ZERO (Vector3f. 3 3 3))]602 (add-joint sphere1 joint)603 (doto (Node. "hinge-world")604 (.attachChild sphere1)605 (.attachChild sphere2))))608 (defn test-joint []609 (view (hinge-world)))611 ;; (defn copier-gen []612 ;; (let [count (atom 0)]613 ;; (fn [in]614 ;; (swap! count inc)615 ;; (clojure.contrib.duck-streams/copy616 ;; in (File. (str "/home/r/tmp/mao-test/clojure-images/"617 ;; ;;/home/r/tmp/mao-test/clojure-images618 ;; (format "%08d.png" @count)))))))619 ;; (defn decrease-framerate []620 ;; (map621 ;; (copier-gen)622 ;; (sort623 ;; (map first624 ;; (partition625 ;; 4626 ;; (filter #(re-matches #".*.png$" (.getCanonicalPath %))627 ;; (file-seq628 ;; (file-str629 ;; "/home/r/media/anime/mao-temp/images"))))))))633 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;635 (defn proprioception636 "Create a proprioception map that reports the rotations of the637 various limbs of the creature's body"638 [creature]639 [#^Node creature]640 (let [641 nodes (node-seq creature)642 joints643 (map644 :joint645 (filter646 #(isa? (class %) JointControl)647 (reduce648 concat649 (map (fn [node]650 (map (fn [num] (.getControl node num))651 (range (.getNumControls node))))652 nodes))))]653 (fn []654 (reduce concat (map relative-positions (list (first joints)))))))657 (defn skel [node]658 (doto659 (.getSkeleton660 (.getControl node SkeletonControl))661 ;; this is necessary to force the skeleton to have accurate world662 ;; transforms before it is rendered to the screen.663 (.resetAndUpdate)))665 (defn green-x-ray []666 (doto (Material. (asset-manager)667 "Common/MatDefs/Misc/Unshaded.j3md")668 (.setColor "Color" ColorRGBA/Green)669 (-> (.getAdditionalRenderState)670 (.setDepthTest false))))672 (defn test-worm []673 (.start674 (world675 (doto (Node.)676 ;;(.attachChild (point-worm))677 (.attachChild (load-blender-model678 "Models/anim2/joint-worm.blend"))680 (.attachChild (box 10 1 10681 :position (Vector3f. 0 -2 0) :mass 0682 :color (ColorRGBA/Gray))))683 {684 "key-space" (fire-cannon-ball)685 }686 (fn [world]687 (enable-debug world)688 (light-up-everything world)689 ;;(.setTimer world (NanoTimer.))690 )691 no-op)))695 ;; defunct movement stuff696 (defn torque-controls [control]697 (let [torques698 (concat699 (map #(Vector3f. 0 (Math/sin %) (Math/cos %))700 (range 0 (* Math/PI 2) (/ (* Math/PI 2) 20)))701 [Vector3f/UNIT_X])]702 (map (fn [torque-axis]703 (fn [torque]704 (.applyTorque705 control706 (.mult (.mult (.getPhysicsRotation control)707 torque-axis)708 (float709 (* (.getMass control) torque))))))710 torques)))712 (defn motor-map713 "Take a creature and generate a function that will enable fine714 grained control over all the creature's limbs."715 [#^Node creature]716 (let [controls (keep #(.getControl % RigidBodyControl)717 (node-seq creature))718 limb-controls (reduce concat (map torque-controls controls))719 body-control (partial map #(%1 %2) limb-controls)]720 body-control))722 (defn test-motor-map723 "see how torque works."724 []725 (let [finger (box 3 0.5 0.5 :position (Vector3f. 0 2 0)726 :mass 1 :color ColorRGBA/Green)727 motor-map (motor-map finger)]728 (world729 (nodify [finger730 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0731 :color ColorRGBA/Gray)])732 standard-debug-controls733 (fn [world]734 (set-gravity world Vector3f/ZERO)735 (light-up-everything world)736 (.setTimer world (NanoTimer.)))737 (fn [_ _]738 (dorun (motor-map [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]))))))741 #+end_src749 * COMMENT generate Source.750 #+begin_src clojure :tangle ../src/cortex/body.clj751 <<proprioception>>752 <<motor-control>>753 #+end_src755 #+begin_src clojure :tangle ../src/cortex/test/body.clj756 <<test-body>>757 #+end_src