annotate org/movement.org @ 271:5833b4ce877a

merged
author Robert McIntyre <rlm@mit.edu>
date Wed, 15 Feb 2012 01:36:16 -0700
parents d487348c461c
children c39b8b29a79e
rev   line source
rlm@260 1 #+title: Simulated Muscles
rlm@158 2 #+author: Robert McIntyre
rlm@158 3 #+email: rlm@mit.edu
rlm@158 4 #+description: muscles for a simulated creature
rlm@158 5 #+keywords: simulation, jMonkeyEngine3, clojure
rlm@158 6 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@158 7 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@158 8
rlm@180 9
rlm@260 10 * Muscles
rlm@260 11
rlm@180 12 Surprisingly enough, terristerial creatures only move by using torque
rlm@180 13 applied about their joints. There's not a single straight line of
rlm@180 14 force in the human body at all! (A straight line of force would
rlm@260 15 correspond to some sort of jet or rocket propulsion.)
rlm@180 16
rlm@260 17 *(next paragraph is from memory and needs to be checked!)*
rlm@180 18
rlm@260 19 In humans, muscles are composed of millions of sarcomeres, which can
rlm@260 20 contract to exert force. A single motor neuron might control 100-1,000
rlm@260 21 sarcomeres. When the motor neuron is engaged by the brain, it
rlm@260 22 activates all of the sarcomeres to which it is attached. Some motor
rlm@260 23 neurons command many sarcomeres, and some command only a few. The
rlm@260 24 spinal cord generally engages the motor neurons which control few
rlm@260 25 sarcomeres before the motor neurons which control many sarcomeres.
rlm@260 26 This recruitment stragety allows for percise movements at low
rlm@260 27 strength. The collection of all motor neurons that control a muscle is
rlm@260 28 called the motor pool. The brain essentially says "activate 30% of the
rlm@260 29 motor pool" and the spinal cord recruits motor neurons untill 30% are
rlm@260 30 activated. Since the distribution of power among motor neurons is
rlm@267 31 unequal and recruitment goes from weakest to strongest, the first 30%
rlm@267 32 of the motor pool might be 5% of the strength of the muscle.
rlm@260 33
rlm@260 34 My simulated muscles follow a similiar design: Each muscle is defined
rlm@267 35 by a 1-D array of numbers (the "motor pool"). Each entry in the array
rlm@267 36 represents a motor neuron which controlls a number of sarcomeres equal
rlm@267 37 to the value of the entry. A muscle also has a scalar :strength factor
rlm@267 38 which determines the total force the muscle can exert when all motor
rlm@267 39 neurons are activated. The effector function for a muscle takes a
rlm@267 40 number to index into the motor pool, and that number "activates" all
rlm@267 41 the motor neurons whose index is lower or equal to the number. Each
rlm@267 42 motor-neuron will apply force in proportion to its value in the array.
rlm@267 43 Lower values cause less force. The lower values can be put at the
rlm@267 44 "beginning" of the 1-D array to simulate the layout of actual human
rlm@267 45 muscles, which are capable of more percise movements when exerting
rlm@267 46 less force. Or, the motor pool can simulate more exoitic recruitment
rlm@267 47 strageties which do not correspond to human muscles.
rlm@260 48
rlm@260 49 This 1D array is defined in an image file for ease of
rlm@260 50 creation/visualization. Here is an example muscle profile image.
rlm@260 51
rlm@260 52 #+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 53 [[../images/basic-muscle.png]]
rlm@260 54
rlm@260 55 * Blender Meta-data
rlm@260 56
rlm@260 57 In blender, each muscle is an empty node whose top level parent is
rlm@260 58 named "muscles", just like eyes, ears, and joints.
rlm@260 59
rlm@260 60 These functions define the expected meta-data for a muscle node.
rlm@180 61
rlm@261 62 #+name: muscle-meta-data
rlm@158 63 #+begin_src clojure
rlm@260 64 (in-ns 'cortex.movement)
rlm@158 65
rlm@180 66 (defvar
rlm@180 67 ^{:arglists '([creature])}
rlm@180 68 muscles
rlm@180 69 (sense-nodes "muscles")
rlm@180 70 "Return the children of the creature's \"muscles\" node.")
rlm@158 71
rlm@260 72 (defn muscle-profile-image
rlm@260 73 "Get the muscle-profile image from the node's blender meta-data."
rlm@260 74 [#^Node muscle]
rlm@260 75 (if-let [image (meta-data muscle "muscle")]
rlm@260 76 (load-image image)))
rlm@260 77
rlm@260 78 (defn muscle-strength
rlm@260 79 "Return the strength of this muscle, or 1 if it is not defined."
rlm@260 80 [#^Node muscle]
rlm@260 81 (if-let [strength (meta-data muscle "strength")]
rlm@260 82 strength 1))
rlm@260 83
rlm@260 84 (defn motor-pool
rlm@260 85 "Return a vector where each entry is the strength of the \"motor
rlm@260 86 neuron\" at that part in the muscle."
rlm@260 87 [#^Node muscle]
rlm@260 88 (let [profile (muscle-profile-image muscle)]
rlm@260 89 (vec
rlm@260 90 (let [width (.getWidth profile)]
rlm@260 91 (for [x (range width)]
rlm@260 92 (- 255
rlm@260 93 (bit-and
rlm@260 94 0x0000FF
rlm@260 95 (.getRGB profile x 0))))))))
rlm@260 96 #+end_src
rlm@260 97
rlm@260 98 Of note here is =(motor-pool)= which interprets the muscle-profile
rlm@260 99 image in a way that allows me to use gradients between white and red,
rlm@260 100 instead of shades of gray as I've been using for all the other
rlm@260 101 senses. This is purely an aesthetic touch.
rlm@260 102
rlm@260 103 * Creating Muscles
rlm@261 104 #+name: muscle-kernel
rlm@260 105 #+begin_src clojure
rlm@261 106 (in-ns 'cortex.movement)
rlm@261 107
rlm@260 108 (defn movement-kernel
rlm@180 109 "Returns a function which when called with a integer value inside a
rlm@191 110 running simulation will cause movement in the creature according
rlm@191 111 to the muscle's position and strength profile. Each function
rlm@191 112 returns the amount of force applied / max force."
rlm@260 113 [#^Node creature #^Node muscle]
rlm@260 114 (let [target (closest-node creature muscle)
rlm@158 115 axis
rlm@158 116 (.mult (.getWorldRotation muscle) Vector3f/UNIT_Y)
rlm@260 117 strength (muscle-strength muscle)
rlm@260 118
rlm@260 119 pool (motor-pool muscle)
rlm@260 120 pool-integral (reductions + pool)
rlm@191 121 force-index
rlm@260 122 (vec (map #(float (* strength (/ % (last pool-integral))))
rlm@260 123 pool-integral))
rlm@158 124 control (.getControl target RigidBodyControl)]
rlm@158 125 (fn [n]
rlm@260 126 (let [pool-index (max 0 (min n (dec (count pool))))
rlm@191 127 force (force-index pool-index)]
rlm@191 128 (.applyTorque control (.mult axis force))
rlm@191 129 (float (/ force strength))))))
rlm@158 130
rlm@180 131 (defn movement!
rlm@180 132 "Endow the creature with the power of movement. Returns a sequence
rlm@180 133 of functions, each of which accept an integer value and will
rlm@180 134 activate their corresponding muscle."
rlm@158 135 [#^Node creature]
rlm@180 136 (for [muscle (muscles creature)]
rlm@260 137 (movement-kernel creature muscle)))
rlm@260 138 #+end_src
rlm@158 139
rlm@260 140 =(movement-kernel)= creates a function that will move the nearest
rlm@260 141 physical object to the muscle node. The muscle exerts a rotational
rlm@260 142 force dependant on it's orientation to the object in the blender
rlm@260 143 file. The function returned by =(movement-kernel)= is also a sense
rlm@260 144 function: it returns the percent of the total muscle strength that is
rlm@260 145 currently being employed. This is analogous to muscle tension in
rlm@260 146 humans and completes the sense of proprioception begun in the last
rlm@260 147 post.
rlm@260 148
rlm@260 149 * Visualizing Muscle Tension
rlm@260 150 Muscle exertion is a percent of a total, so the visulazation is just a
rlm@260 151 simple percent bar.
rlm@260 152
rlm@261 153 #+name: visualization
rlm@260 154 #+begin_src clojure
rlm@191 155 (defn movement-display-kernel
rlm@191 156 "Display muscle exertion data as a bar filling up with red."
rlm@191 157 [exertion]
rlm@191 158 (let [height 20
rlm@191 159 width 300
rlm@191 160 image (BufferedImage. width height
rlm@191 161 BufferedImage/TYPE_INT_RGB)
rlm@191 162 fill (min (int (* width exertion)) width)]
rlm@191 163 (dorun
rlm@191 164 (for [x (range fill)
rlm@191 165 y (range height)]
rlm@191 166 (.setRGB image x y 0xFF0000)))
rlm@191 167 image))
rlm@191 168
rlm@191 169 (defn view-movement
rlm@191 170 "Creates a function which accepts a list of muscle-exertion data and
rlm@191 171 displays each element of the list to the screen."
rlm@191 172 []
rlm@191 173 (view-sense movement-display-kernel))
rlm@158 174 #+end_src
rlm@158 175
rlm@260 176 * Adding Touch to the Worm
rlm@158 177
rlm@261 178 #+begin_src clojure
rlm@261 179 (defn test-movement
rlm@261 180 ([] (test-movement false))
rlm@261 181 ([record?]
rlm@261 182 (let [creature (doto (worm) (body!))
rlm@261 183
rlm@261 184 muscle-exertion (atom 0)
rlm@261 185 muscles (movement! creature)
rlm@261 186 muscle-display (view-movement)]
rlm@261 187 (.setMass
rlm@261 188 (.getControl (.getChild creature "worm-11") RigidBodyControl)
rlm@261 189 (float 0))
rlm@261 190 (world
rlm@261 191 (nodify [creature (floor)])
rlm@261 192 (merge standard-debug-controls
rlm@261 193 {"key-h"
rlm@261 194 (fn [_ value]
rlm@261 195 (if value
rlm@261 196 (swap! muscle-exertion (partial + 20))))
rlm@261 197 "key-n"
rlm@261 198 (fn [_ value]
rlm@261 199 (if value
rlm@261 200 (swap! muscle-exertion (fn [v] (- v 20)))))})
rlm@261 201 (fn [world]
rlm@261 202 (if record?
rlm@261 203 (Capture/captureVideo
rlm@261 204 world
rlm@261 205 (File. "/home/r/proj/cortex/render/worm-muscles/main-view")))
rlm@261 206 (light-up-everything world)
rlm@261 207 (enable-debug world)
rlm@261 208 (.setTimer world (RatchetTimer. 60))
rlm@261 209 (set-gravity world (Vector3f. 0 0 0))
rlm@261 210 (.setLocation (.getCamera world)
rlm@261 211 (Vector3f. -4.912815, 2.004171, 0.15710819))
rlm@261 212 (.setRotation (.getCamera world)
rlm@261 213 (Quaternion. 0.13828252, 0.65516764,
rlm@261 214 -0.12370994, 0.7323449))
rlm@261 215
rlm@261 216 (comment
rlm@261 217 (com.aurellem.capture.Capture/captureVideo
rlm@261 218 world (file-str "/home/r/proj/ai-videos/hand"))))
rlm@261 219 (fn [world tpf]
rlm@261 220 (muscle-display
rlm@261 221 (map #(% @muscle-exertion) muscles)
rlm@261 222 (if record?
rlm@261 223 (File. "/home/r/proj/cortex/render/worm-muscles/muscles"))))))))
rlm@261 224 #+end_src
rlm@261 225
rlm@261 226 * Video Demonstration
rlm@261 227
rlm@261 228 #+begin_html
rlm@261 229 <div class="figure">
rlm@261 230 <center>
rlm@261 231 <video controls="controls" width="550">
rlm@261 232 <source src="../video/worm-muscles.ogg" type="video/ogg"
rlm@261 233 preload="none" poster="../images/aurellem-1280x480.png" />
rlm@261 234 </video>
rlm@261 235 </center>
rlm@261 236 <p>The worm is now able to move. The bar in the lower right displays
rlm@261 237 the power output of the muscle . Each jump causes 20 more motor neurons to
rlm@261 238 be recruited. Notice that the power output increases non-linearly
rlm@261 239 with motror neuron recruitement, similiar to a human muscle.</p>
rlm@261 240 </div>
rlm@261 241 #+end_html
rlm@261 242
rlm@261 243
rlm@261 244 ** Making the Worm Muscles Video
rlm@261 245 #+name: magick7
rlm@261 246 #+begin_src clojure
rlm@261 247 (ns cortex.video.magick7
rlm@261 248 (:import java.io.File)
rlm@261 249 (:use clojure.contrib.shell-out))
rlm@261 250
rlm@261 251 (defn images [path]
rlm@261 252 (sort (rest (file-seq (File. path)))))
rlm@261 253
rlm@261 254 (def base "/home/r/proj/cortex/render/worm-muscles/")
rlm@261 255
rlm@261 256 (defn pics [file]
rlm@261 257 (images (str base file)))
rlm@261 258
rlm@261 259 (defn combine-images []
rlm@261 260 (let [main-view (pics "main-view")
rlm@261 261 muscles (pics "muscles/0")
rlm@261 262 targets (map
rlm@261 263 #(File. (str base "out/" (format "%07d.png" %)))
rlm@261 264 (range 0 (count main-view)))]
rlm@261 265 (dorun
rlm@261 266 (pmap
rlm@261 267 (comp
rlm@261 268 (fn [[ main-view muscles target]]
rlm@261 269 (println target)
rlm@261 270 (sh "convert"
rlm@261 271 main-view
rlm@261 272 muscles "-geometry" "+320+440" "-composite"
rlm@261 273 target))
rlm@261 274 (fn [& args] (map #(.getCanonicalPath %) args)))
rlm@261 275 main-view muscles targets))))
rlm@261 276 #+end_src
rlm@261 277
rlm@261 278 #+begin_src sh :results silent
rlm@261 279 cd ~/proj/cortex/render/worm-muscles
rlm@261 280 ffmpeg -r 60 -i out/%07d.png -b:v 9000k -c:v libtheora worm-muscles.ogg
rlm@261 281 #+end_src
rlm@158 282
rlm@260 283 * Headers
rlm@260 284 #+name: muscle-header
rlm@260 285 #+begin_src clojure
rlm@260 286 (ns cortex.movement
rlm@260 287 "Give simulated creatures defined in special blender files the power
rlm@260 288 to move around in a simulated environment."
rlm@260 289 {:author "Robert McIntyre"}
rlm@260 290 (:use (cortex world util sense body))
rlm@260 291 (:use clojure.contrib.def)
rlm@260 292 (:import java.awt.image.BufferedImage)
rlm@260 293 (:import com.jme3.scene.Node)
rlm@260 294 (:import com.jme3.math.Vector3f)
rlm@260 295 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@260 296 #+end_src
rlm@260 297
rlm@261 298 #+name: test-header
rlm@261 299 #+begin_src clojure
rlm@261 300 (ns cortex.test.movement
rlm@261 301 (:use (cortex world util sense body movement))
rlm@261 302 (:use cortex.test.body)
rlm@261 303 (:use clojure.contrib.def)
rlm@261 304 (:import java.io.File)
rlm@261 305 (:import java.awt.image.BufferedImage)
rlm@261 306 (:import com.jme3.scene.Node)
rlm@261 307 (:import com.jme3.math.Vector3f)
rlm@261 308 (:import (com.aurellem.capture Capture RatchetTimer))
rlm@261 309 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@158 310
rlm@261 311 (cortex.import/mega-import-jme3)
rlm@261 312 #+end_src
rlm@261 313
rlm@261 314 * Source Listing
rlm@261 315 - [[../src/cortex/movement.clj][cortex.movement]]
rlm@261 316 - [[../src/cortex/test/movement.clj][cortex.test.movement]]
rlm@261 317 - [[../src/cortex/video/magick7.clj][cortex.video.magick7]]
rlm@261 318 #+html: <ul> <li> <a href="../org/movement.org">This org file</a> </li> </ul>
rlm@261 319 - [[http://hg.bortreb.com ][source-repository]]
rlm@158 320
rlm@158 321 * COMMENT code generation
rlm@158 322 #+begin_src clojure :tangle ../src/cortex/movement.clj
rlm@261 323 <<muscle-header>>
rlm@261 324 <<muscle-meta-data>>
rlm@261 325 <<muscle-kernel>>
rlm@261 326 <<visualization>>
rlm@158 327 #+end_src
rlm@261 328
rlm@261 329 #+begin_src clojure :tangle ../src/cortex/test/movement.clj
rlm@261 330 <<test-header>>
rlm@261 331 <<test-movement>>
rlm@261 332 #+end_src
rlm@261 333
rlm@261 334 #+begin_src clojure :tangle ../src/cortex/video/magick7.clj
rlm@261 335 <<magick7>>
rlm@261 336 #+end_src