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 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
10 * Proprioception
11 #+name: proprioception
12 #+begin_src clojure
13 (ns cortex.body
14 (:use (cortex world util))
15 (:import
16 com.jme3.math.Vector3f
17 com.jme3.math.Quaternion
18 com.jme3.math.Vector2f
19 com.jme3.math.Matrix3f
20 com.jme3.bullet.control.RigidBodyControl))
22 (defn any-orthogonal
23 "Generate an arbitray (but stable) orthogonal vector to a given
24 vector."
25 [vector]
26 (let [x (.getX vector)
27 y (.getY vector)
28 z (.getZ vector)]
29 (cond
30 (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-quaternion
36 "From http://stackoverflow.com/questions/3684269/
37 component-of-a-quaternion-rotation-around-an-axis.
39 Determine the amount of rotation a quaternion will
40 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-proprioception
50 "Relative position information for a two-part system connected by a
51 joint. Gives the pitch, yaw, and roll of the 'B' object relative to
52 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-a
57 (.normalize
58 (.subtract
59 (.localToWorld object-a (.getPivotA joint) nil)
60 (.getWorldTranslation object-a)))
61 rotate-a
62 (doto (Matrix3f.)
63 (.fromStartEndVectors arm-a Vector3f/UNIT_X))
64 arm-b
65 (.mult
66 rotate-a
67 (.normalize
68 (.subtract
69 (.localToWorld object-b (.getPivotB joint) nil)
70 (.getWorldTranslation object-b))))
71 pitch
72 (.angleBetween
73 (.normalize (Vector2f. (.getX arm-b) (.getY arm-b)))
74 (Vector2f. 1 0))
75 yaw
76 (.angleBetween
77 (.normalize (Vector2f. (.getX arm-b) (.getZ arm-b)))
78 (Vector2f. 1 0))
80 roll
81 (project-quaternion
82 (.mult
83 (.getLocalRotation object-b)
84 (doto (Quaternion.)
85 (.fromRotationMatrix rotate-a)))
86 arm-b)]
87 [pitch yaw roll]))
89 (defn proprioception
90 "Create a function that provides proprioceptive information about an
91 entire body."
92 [body]
93 ;; extract the body's joints
94 (let [joints
95 (distinct
96 (reduce
97 concat
98 (map #(.getJoints %)
99 (keep
100 #(.getControl % RigidBodyControl)
101 (node-seq body)))))]
102 (fn []
103 (map joint-proprioception joints))))
105 #+end_src
107 * Motor Control
108 #+name: motor-control
109 #+begin_src clojure
110 (in-ns 'cortex.body)
112 ;; surprisingly enough, terristerial creatures only move by using
113 ;; torque applied about their joints. There's not a single straight
114 ;; line of force in the human body at all! (A straight line of force
115 ;; would correspond to some sort of jet or rocket propulseion.)
117 (defn vector-motor-control
118 "Create a function that accepts a sequence of Vector3f objects that
119 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_src
129 ## note -- might want to add a lower dimensional, discrete version of
130 ## this if it proves useful from a x-modal clustering perspective.
132 * Examples
134 #+name: test-body
135 #+begin_src clojure
136 (ns cortex.test.body
137 (:use (cortex world util body))
138 (:import
139 com.jme3.math.Vector3f
140 com.jme3.math.ColorRGBA
141 com.jme3.bullet.joints.Point2PointJoint
142 com.jme3.bullet.control.RigidBodyControl
143 com.jme3.system.NanoTimer))
145 (defn worm-segments
146 "Create multiple evenly spaced box segments. They're fabulous!"
147 [segment-length num-segments interstitial-space radius]
148 (letfn [(nth-segment
149 [n]
150 (box segment-length radius radius :mass 0.1
151 :position
152 (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-midpoint
159 "Connect two physics objects with a Point2Point joint constraint at
160 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 registers
169 ;; it with both physics objects which in turn
170 ;; will register the joint with the physics system
171 ;; when the simulation is started.
172 joint (Point2PointJoint.
173 (.getControl segmentA RigidBodyControl)
174 (.getControl segmentB RigidBodyControl)
175 pivotA
176 pivotB)]
177 segmentB))
179 (defn eve-worm
180 "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-pattern
188 "This is a simple, mindless motor control pattern that drives the
189 second segment of the worm's body at an offset angle with
190 sinusoidally varying strength."
191 [time]
192 (let [angle (* Math/PI (/ 9 20))
193 direction (Vector3f. 0 (Math/sin angle) (Math/cos angle))]
194 [Vector3f/ZERO
195 (.mult
196 direction
197 (float (* 2 (Math/sin (* Math/PI 2 (/ (rem time 300 ) 300))))))
198 Vector3f/ZERO
199 Vector3f/ZERO
200 Vector3f/ZERO]))
202 (defn test-motor-control
203 "Testing motor-control:
204 You should see a multi-segmented worm-like object fall onto the
205 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 (world
211 (nodify [worm
212 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
213 :color ColorRGBA/Gray)])
214 standard-debug-controls
215 (fn [world]
216 (enable-debug world)
217 (light-up-everything world)
218 (comment
219 (com.aurellem.capture.Capture/captureVideo
220 world
221 (file-str "/home/r/proj/cortex/tmp/moving-worm")))
222 )
224 (fn [_ _]
225 (swap! time inc)
226 (Thread/sleep 20)
227 (dorun (worm-motor-map
228 (worm-pattern @time)))))))
230 (defn test-proprioception
231 "Testing proprioception:
232 You should see two foating bars, and a printout of pitch, yaw, and
233 roll. Pressing key-r/key-t should move the blue bar up and down and
234 change only the value of pitch. key-f/key-g moves it side to side
235 and changes yaw. key-v/key-b will spin the blue segment clockwise
236 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 joint
253 (doto
254 (Point2PointJoint.
255 (.getControl hand RigidBodyControl)
256 control
257 (Vector3f. 1.2 0 0)
258 (Vector3f. -1.2 0 0 ))
259 (.setCollisionBetweenLinkedBodys false))
260 time (atom 0)]
261 (world
262 (nodify [hand finger floor])
263 (merge standard-debug-controls
264 {"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 control
279 (.mult (.getPhysicsRotation control)
280 (Vector3f. 0 0 10))))
281 (if @move-down?
282 (.applyTorque control
283 (.mult (.getPhysicsRotation control)
284 (Vector3f. 0 0 -10))))
285 (if @move-left?
286 (.applyTorque control
287 (.mult (.getPhysicsRotation control)
288 (Vector3f. 0 10 0))))
289 (if @move-right?
290 (.applyTorque control
291 (.mult (.getPhysicsRotation control)
292 (Vector3f. 0 -10 0))))
293 (if @roll-left?
294 (.applyTorque control
295 (.mult (.getPhysicsRotation control)
296 (Vector3f. -1 0 0))))
297 (if @roll-right?
298 (.applyTorque control
299 (.mult (.getPhysicsRotation control)
300 (Vector3f. 1 0 0))))
302 (if (= 0 (rem (swap! time inc) 2000))
303 (do
304 (apply
305 (comp
306 println-repl
307 #(format "pitch: %1.2f\nyaw: %1.2f\nroll: %1.2f\n" %1 %2 %3))
308 (joint-proprioception joint))))))))
309 #+end_src
311 #+results: test-body
312 : #'test.body/test-proprioception
316 * COMMENT code-limbo
317 #+begin_src clojure
318 ;;(.loadModel
319 ;; (doto (asset-manager)
320 ;; (.registerLoader BlenderModelLoader (into-array String ["blend"])))
321 ;; "Models/person/person.blend")
324 (defn load-blender-model
325 "Load a .blend file using an asset folder relative path."
326 [^String model]
327 (.loadModel
328 (doto (asset-manager)
329 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
330 model))
333 (defn view-model [^String model]
334 (view
335 (.loadModel
336 (doto (asset-manager)
337 (.registerLoader BlenderModelLoader (into-array String ["blend"])))
338 model)))
340 (defn load-blender-scene [^String model]
341 (.loadModel
342 (doto (asset-manager)
343 (.registerLoader BlenderLoader (into-array String ["blend"])))
344 model))
346 (defn worm
347 []
348 (.loadModel (asset-manager) "Models/anim2/Cube.mesh.xml"))
350 (defn oto
351 []
352 (.loadModel (asset-manager) "Models/Oto/Oto.mesh.xml"))
354 (defn sinbad
355 []
356 (.loadModel (asset-manager) "Models/Sinbad/Sinbad.mesh.xml"))
358 (defn worm-blender
359 []
360 (first (seq (.getChildren (load-blender-model
361 "Models/anim2/simple-worm.blend")))))
363 (defn body
364 "given a node with a SkeletonControl, will produce a body sutiable
365 for AI control with movement and proprioception."
366 [node]
367 (let [skeleton-control (.getControl node SkeletonControl)
368 krc (KinematicRagdollControl.)]
369 (comment
370 (dorun
371 (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 node
379 )
380 (defn show-skeleton [node]
381 (let [sd
383 (doto
384 (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 like
395 ;; being eyes and the like
397 (.getUserData
398 (.getChild
399 (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-node
409 [f debug-node skeleton]
410 (let [bones
411 (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 (dorun
419 (map #(.attachChild
420 debug-node
421 (doto
422 (sphere 0.1
423 :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 (dorun
437 (map
438 #(.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 b
446 Vector3f/UNIT_X
447 Quaternion/IDENTITY
448 ;;(doto (Quaternion.)
449 ;; (.fromAngles (/ Math/PI 2)
450 ;; 0
451 ;; 0
453 (Vector3f. 1 1 1))
454 )
456 (println-repl "hi! <3"))))
459 (defn test-ragdoll []
461 (let [the-worm
463 ;;(.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 (.start
474 (world
475 (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/createPhysicsTestWorld
483 (.getRootNode world)
484 (asset-manager)
485 (.getPhysicsSpace
486 (.getState (.getStateManager world) BulletAppState)))
487 (set-gravity world Vector3f/ZERO)
488 ;;(.setTimer world (NanoTimer.))
489 ;;(org.lwjgl.input.Mouse/setGrabbed false)
490 )
491 no-op
492 )
495 )))
498 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
499 ;;; here is the ragdoll stuff
501 (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 (.get
511 (.getFloatBuffer worm-mesh VertexBuffer$Type/Position)
512 index))
514 (defn bones [index]
515 (.get
516 (.getData (.getBuffer mesh VertexBuffer$Type/BoneIndex))
517 index))
519 (defn bone-weights [index]
520 (.get
521 (.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/Red
542 [1 2 0 0] ColorRGBA/Magenta
543 [2 0 0 0] ColorRGBA/Blue}
544 (vertex-bones index)
545 ColorRGBA/White))
547 (defn influence-color [index bone-num]
548 (get
549 {(float 0) ColorRGBA/Blue
550 (float 0.5) ColorRGBA/Green
551 (float 1) ColorRGBA/Red}
552 ;; find the weight of the desired bone
553 ((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 (dorun
563 (map #(.attachChild points %)
564 (map #(sphere 0.01
565 :position (vertex-position %)
566 :color (influence-color % 1)
567 :physical? false)
568 (range 60))))
569 (view points)))
572 (defrecord JointControl [joint physics-space]
573 PhysicsControl
574 (setPhysicsSpace [this space]
575 (dosync
576 (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-joint
586 "Add a joint to a particular object. When the object is added to the
587 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-world
595 []
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/copy
616 ;; in (File. (str "/home/r/tmp/mao-test/clojure-images/"
617 ;; ;;/home/r/tmp/mao-test/clojure-images
618 ;; (format "%08d.png" @count)))))))
619 ;; (defn decrease-framerate []
620 ;; (map
621 ;; (copier-gen)
622 ;; (sort
623 ;; (map first
624 ;; (partition
625 ;; 4
626 ;; (filter #(re-matches #".*.png$" (.getCanonicalPath %))
627 ;; (file-seq
628 ;; (file-str
629 ;; "/home/r/media/anime/mao-temp/images"))))))))
633 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
635 (defn proprioception
636 "Create a proprioception map that reports the rotations of the
637 various limbs of the creature's body"
638 [creature]
639 [#^Node creature]
640 (let [
641 nodes (node-seq creature)
642 joints
643 (map
644 :joint
645 (filter
646 #(isa? (class %) JointControl)
647 (reduce
648 concat
649 (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 (doto
659 (.getSkeleton
660 (.getControl node SkeletonControl))
661 ;; this is necessary to force the skeleton to have accurate world
662 ;; 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 (.start
674 (world
675 (doto (Node.)
676 ;;(.attachChild (point-worm))
677 (.attachChild (load-blender-model
678 "Models/anim2/joint-worm.blend"))
680 (.attachChild (box 10 1 10
681 :position (Vector3f. 0 -2 0) :mass 0
682 :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 stuff
696 (defn torque-controls [control]
697 (let [torques
698 (concat
699 (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 (.applyTorque
705 control
706 (.mult (.mult (.getPhysicsRotation control)
707 torque-axis)
708 (float
709 (* (.getMass control) torque))))))
710 torques)))
712 (defn motor-map
713 "Take a creature and generate a function that will enable fine
714 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-map
723 "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 (world
729 (nodify [finger
730 (box 10 0.5 10 :position (Vector3f. 0 -5 0) :mass 0
731 :color ColorRGBA/Gray)])
732 standard-debug-controls
733 (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_src
749 * COMMENT generate Source.
750 #+begin_src clojure :tangle ../src/cortex/body.clj
751 <<proprioception>>
752 <<motor-control>>
753 #+end_src
755 #+begin_src clojure :tangle ../src/cortex/test/body.clj
756 <<test-body>>
757 #+end_src