rlm@260: #+title: Simulated Muscles rlm@158: #+author: Robert McIntyre rlm@158: #+email: rlm@mit.edu rlm@158: #+description: muscles for a simulated creature rlm@158: #+keywords: simulation, jMonkeyEngine3, clojure rlm@158: #+SETUPFILE: ../../aurellem/org/setup.org rlm@158: #+INCLUDE: ../../aurellem/org/level-0.org rlm@158: rlm@180: rlm@260: * Muscles rlm@260: rlm@180: Surprisingly enough, terristerial creatures only move by using torque rlm@180: applied about their joints. There's not a single straight line of rlm@180: force in the human body at all! (A straight line of force would rlm@260: correspond to some sort of jet or rocket propulsion.) rlm@180: rlm@260: *(next paragraph is from memory and needs to be checked!)* rlm@180: rlm@260: In humans, muscles are composed of millions of sarcomeres, which can rlm@260: contract to exert force. A single motor neuron might control 100-1,000 rlm@260: sarcomeres. When the motor neuron is engaged by the brain, it rlm@260: activates all of the sarcomeres to which it is attached. Some motor rlm@260: neurons command many sarcomeres, and some command only a few. The rlm@260: spinal cord generally engages the motor neurons which control few rlm@260: sarcomeres before the motor neurons which control many sarcomeres. rlm@260: This recruitment stragety allows for percise movements at low rlm@260: strength. The collection of all motor neurons that control a muscle is rlm@260: called the motor pool. The brain essentially says "activate 30% of the rlm@260: motor pool" and the spinal cord recruits motor neurons untill 30% are rlm@260: activated. Since the distribution of power among motor neurons is rlm@260: unequal and recruitment goes from weakest to strongest, 30% of the rlm@260: motor pool might be 5% of the strength of the muscle. rlm@260: rlm@260: My simulated muscles follow a similiar design: Each muscle is defined rlm@260: by a 1-D array of numbers (the "motor pool"). Each number represents a rlm@260: motor neuron which controlls a number of sarcomeres equal to the rlm@260: number. A muscle also has a scalar :strength factor which determines rlm@260: the total force the muscle can exert when all motor neurons are rlm@260: activated. The effector function for a muscle takes a number to index rlm@260: into the motor pool, and that number "activates" all the motor neurons rlm@260: whose index is lower or equal to the number. Each motor-neuron will rlm@260: apply force in proportion to its value in the array. Lower values rlm@260: cause less force. The lower values can be put at the "beginning" of rlm@260: the 1-D array to simulate the layout of actual human muscles, which rlm@260: are capable of more percise movements when exerting less force. Or, rlm@260: the motor pool can simulate more exoitic recruitment strageties which rlm@260: do not correspond to human muscles. rlm@260: rlm@260: This 1D array is defined in an image file for ease of rlm@260: creation/visualization. Here is an example muscle profile image. rlm@260: rlm@260: #+caption: A muscle profile image that describes the strengths of each motor neuron in a muscle. White is weakest and dark red is strongest. This particular pattern has weaker motor neurons at the beginning, just like human muscle. rlm@260: [[../images/basic-muscle.png]] rlm@260: rlm@260: * Blender Meta-data rlm@260: rlm@260: In blender, each muscle is an empty node whose top level parent is rlm@260: named "muscles", just like eyes, ears, and joints. rlm@260: rlm@260: These functions define the expected meta-data for a muscle node. rlm@180: rlm@261: #+name: muscle-meta-data rlm@158: #+begin_src clojure rlm@260: (in-ns 'cortex.movement) rlm@158: rlm@180: (defvar rlm@180: ^{:arglists '([creature])} rlm@180: muscles rlm@180: (sense-nodes "muscles") rlm@180: "Return the children of the creature's \"muscles\" node.") rlm@158: rlm@260: (defn muscle-profile-image rlm@260: "Get the muscle-profile image from the node's blender meta-data." rlm@260: [#^Node muscle] rlm@260: (if-let [image (meta-data muscle "muscle")] rlm@260: (load-image image))) rlm@260: rlm@260: (defn muscle-strength rlm@260: "Return the strength of this muscle, or 1 if it is not defined." rlm@260: [#^Node muscle] rlm@260: (if-let [strength (meta-data muscle "strength")] rlm@260: strength 1)) rlm@260: rlm@260: (defn motor-pool rlm@260: "Return a vector where each entry is the strength of the \"motor rlm@260: neuron\" at that part in the muscle." rlm@260: [#^Node muscle] rlm@260: (let [profile (muscle-profile-image muscle)] rlm@260: (vec rlm@260: (let [width (.getWidth profile)] rlm@260: (for [x (range width)] rlm@260: (- 255 rlm@260: (bit-and rlm@260: 0x0000FF rlm@260: (.getRGB profile x 0)))))))) rlm@260: #+end_src rlm@260: rlm@260: Of note here is =(motor-pool)= which interprets the muscle-profile rlm@260: image in a way that allows me to use gradients between white and red, rlm@260: instead of shades of gray as I've been using for all the other rlm@260: senses. This is purely an aesthetic touch. rlm@260: rlm@260: * Creating Muscles rlm@261: #+name: muscle-kernel rlm@260: #+begin_src clojure rlm@261: (in-ns 'cortex.movement) rlm@261: rlm@260: (defn movement-kernel rlm@180: "Returns a function which when called with a integer value inside a rlm@191: running simulation will cause movement in the creature according rlm@191: to the muscle's position and strength profile. Each function rlm@191: returns the amount of force applied / max force." rlm@260: [#^Node creature #^Node muscle] rlm@260: (let [target (closest-node creature muscle) rlm@158: axis rlm@158: (.mult (.getWorldRotation muscle) Vector3f/UNIT_Y) rlm@260: strength (muscle-strength muscle) rlm@260: rlm@260: pool (motor-pool muscle) rlm@260: pool-integral (reductions + pool) rlm@191: force-index rlm@260: (vec (map #(float (* strength (/ % (last pool-integral)))) rlm@260: pool-integral)) rlm@158: control (.getControl target RigidBodyControl)] rlm@158: (fn [n] rlm@260: (let [pool-index (max 0 (min n (dec (count pool)))) rlm@191: force (force-index pool-index)] rlm@191: (.applyTorque control (.mult axis force)) rlm@191: (float (/ force strength)))))) rlm@158: rlm@180: (defn movement! rlm@180: "Endow the creature with the power of movement. Returns a sequence rlm@180: of functions, each of which accept an integer value and will rlm@180: activate their corresponding muscle." rlm@158: [#^Node creature] rlm@180: (for [muscle (muscles creature)] rlm@260: (movement-kernel creature muscle))) rlm@260: #+end_src rlm@158: rlm@260: =(movement-kernel)= creates a function that will move the nearest rlm@260: physical object to the muscle node. The muscle exerts a rotational rlm@260: force dependant on it's orientation to the object in the blender rlm@260: file. The function returned by =(movement-kernel)= is also a sense rlm@260: function: it returns the percent of the total muscle strength that is rlm@260: currently being employed. This is analogous to muscle tension in rlm@260: humans and completes the sense of proprioception begun in the last rlm@260: post. rlm@260: rlm@260: * Visualizing Muscle Tension rlm@260: Muscle exertion is a percent of a total, so the visulazation is just a rlm@260: simple percent bar. rlm@260: rlm@261: #+name: visualization rlm@260: #+begin_src clojure rlm@191: (defn movement-display-kernel rlm@191: "Display muscle exertion data as a bar filling up with red." rlm@191: [exertion] rlm@191: (let [height 20 rlm@191: width 300 rlm@191: image (BufferedImage. width height rlm@191: BufferedImage/TYPE_INT_RGB) rlm@191: fill (min (int (* width exertion)) width)] rlm@191: (dorun rlm@191: (for [x (range fill) rlm@191: y (range height)] rlm@191: (.setRGB image x y 0xFF0000))) rlm@191: image)) rlm@191: rlm@191: (defn view-movement rlm@191: "Creates a function which accepts a list of muscle-exertion data and rlm@191: displays each element of the list to the screen." rlm@191: [] rlm@191: (view-sense movement-display-kernel)) rlm@158: #+end_src rlm@158: rlm@260: * Adding Touch to the Worm rlm@158: rlm@261: #+begin_src clojure rlm@261: (defn test-movement rlm@261: ([] (test-movement false)) rlm@261: ([record?] rlm@261: (let [creature (doto (worm) (body!)) rlm@261: rlm@261: muscle-exertion (atom 0) rlm@261: muscles (movement! creature) rlm@261: muscle-display (view-movement)] rlm@261: (.setMass rlm@261: (.getControl (.getChild creature "worm-11") RigidBodyControl) rlm@261: (float 0)) rlm@261: (world rlm@261: (nodify [creature (floor)]) rlm@261: (merge standard-debug-controls rlm@261: {"key-h" rlm@261: (fn [_ value] rlm@261: (if value rlm@261: (swap! muscle-exertion (partial + 20)))) rlm@261: "key-n" rlm@261: (fn [_ value] rlm@261: (if value rlm@261: (swap! muscle-exertion (fn [v] (- v 20)))))}) rlm@261: (fn [world] rlm@261: (if record? rlm@261: (Capture/captureVideo rlm@261: world rlm@261: (File. "/home/r/proj/cortex/render/worm-muscles/main-view"))) rlm@261: (light-up-everything world) rlm@261: (enable-debug world) rlm@261: (.setTimer world (RatchetTimer. 60)) rlm@261: (set-gravity world (Vector3f. 0 0 0)) rlm@261: (.setLocation (.getCamera world) rlm@261: (Vector3f. -4.912815, 2.004171, 0.15710819)) rlm@261: (.setRotation (.getCamera world) rlm@261: (Quaternion. 0.13828252, 0.65516764, rlm@261: -0.12370994, 0.7323449)) rlm@261: rlm@261: (comment rlm@261: (com.aurellem.capture.Capture/captureVideo rlm@261: world (file-str "/home/r/proj/ai-videos/hand")))) rlm@261: (fn [world tpf] rlm@261: (muscle-display rlm@261: (map #(% @muscle-exertion) muscles) rlm@261: (if record? rlm@261: (File. "/home/r/proj/cortex/render/worm-muscles/muscles")))))))) rlm@261: #+end_src rlm@261: rlm@261: * Video Demonstration rlm@261: rlm@261: #+begin_html rlm@261:
The worm is now able to move. The bar in the lower right displays rlm@261: the power output of the muscle . Each jump causes 20 more motor neurons to rlm@261: be recruited. Notice that the power output increases non-linearly rlm@261: with motror neuron recruitement, similiar to a human muscle.
rlm@261: