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@267: unequal and recruitment goes from weakest to strongest, the first 30% rlm@267: of the 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@267: by a 1-D array of numbers (the "motor pool"). Each entry in the array rlm@267: represents a motor neuron which controlls a number of sarcomeres equal rlm@267: to the value of the entry. A muscle also has a scalar :strength factor rlm@267: which determines the total force the muscle can exert when all motor rlm@267: neurons are activated. The effector function for a muscle takes a rlm@267: number to index into the motor pool, and that number "activates" all rlm@267: the motor neurons whose index is lower or equal to the number. Each rlm@267: motor-neuron will apply force in proportion to its value in the array. rlm@267: Lower values cause less force. The lower values can be put at the rlm@267: "beginning" of the 1-D array to simulate the layout of actual human rlm@267: muscles, which are capable of more percise movements when exerting rlm@267: less force. Or, the motor pool can simulate more exoitic recruitment rlm@267: strageties which 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@273: 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@273: =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@273: 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:
rlm@261:
rlm@261: rlm@261:
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:
rlm@261: #+end_html rlm@261: rlm@261: rlm@261: ** Making the Worm Muscles Video rlm@261: #+name: magick7 rlm@261: #+begin_src clojure rlm@261: (ns cortex.video.magick7 rlm@261: (:import java.io.File) rlm@261: (:use clojure.contrib.shell-out)) rlm@261: rlm@261: (defn images [path] rlm@261: (sort (rest (file-seq (File. path))))) rlm@261: rlm@261: (def base "/home/r/proj/cortex/render/worm-muscles/") rlm@261: rlm@261: (defn pics [file] rlm@261: (images (str base file))) rlm@261: rlm@261: (defn combine-images [] rlm@261: (let [main-view (pics "main-view") rlm@261: muscles (pics "muscles/0") rlm@261: targets (map rlm@261: #(File. (str base "out/" (format "%07d.png" %))) rlm@261: (range 0 (count main-view)))] rlm@261: (dorun rlm@261: (pmap rlm@261: (comp rlm@261: (fn [[ main-view muscles target]] rlm@261: (println target) rlm@261: (sh "convert" rlm@261: main-view rlm@261: muscles "-geometry" "+320+440" "-composite" rlm@261: target)) rlm@261: (fn [& args] (map #(.getCanonicalPath %) args))) rlm@261: main-view muscles targets)))) rlm@261: #+end_src rlm@261: rlm@261: #+begin_src sh :results silent rlm@261: cd ~/proj/cortex/render/worm-muscles rlm@261: ffmpeg -r 60 -i out/%07d.png -b:v 9000k -c:v libtheora worm-muscles.ogg rlm@261: #+end_src rlm@158: rlm@260: * Headers rlm@260: #+name: muscle-header rlm@260: #+begin_src clojure rlm@260: (ns cortex.movement rlm@260: "Give simulated creatures defined in special blender files the power rlm@260: to move around in a simulated environment." rlm@260: {:author "Robert McIntyre"} rlm@260: (:use (cortex world util sense body)) rlm@260: (:use clojure.contrib.def) rlm@260: (:import java.awt.image.BufferedImage) rlm@260: (:import com.jme3.scene.Node) rlm@260: (:import com.jme3.math.Vector3f) rlm@260: (:import com.jme3.bullet.control.RigidBodyControl)) rlm@260: #+end_src rlm@260: rlm@261: #+name: test-header rlm@261: #+begin_src clojure rlm@261: (ns cortex.test.movement rlm@261: (:use (cortex world util sense body movement)) rlm@261: (:use cortex.test.body) rlm@261: (:use clojure.contrib.def) rlm@261: (:import java.io.File) rlm@261: (:import java.awt.image.BufferedImage) rlm@261: (:import com.jme3.scene.Node) rlm@261: (:import com.jme3.math.Vector3f) rlm@261: (:import (com.aurellem.capture Capture RatchetTimer)) rlm@261: (:import com.jme3.bullet.control.RigidBodyControl)) rlm@158: rlm@261: (cortex.import/mega-import-jme3) rlm@261: #+end_src rlm@261: rlm@261: * Source Listing rlm@261: - [[../src/cortex/movement.clj][cortex.movement]] rlm@261: - [[../src/cortex/test/movement.clj][cortex.test.movement]] rlm@261: - [[../src/cortex/video/magick7.clj][cortex.video.magick7]] rlm@261: #+html: rlm@261: - [[http://hg.bortreb.com ][source-repository]] rlm@158: rlm@158: * COMMENT code generation rlm@158: #+begin_src clojure :tangle ../src/cortex/movement.clj rlm@261: <> rlm@261: <> rlm@261: <> rlm@261: <> rlm@158: #+end_src rlm@261: rlm@261: #+begin_src clojure :tangle ../src/cortex/test/movement.clj rlm@261: <> rlm@261: <> rlm@261: #+end_src rlm@261: rlm@261: #+begin_src clojure :tangle ../src/cortex/video/magick7.clj rlm@261: <> rlm@261: #+end_src