annotate org/movement.org @ 277:bded932ef696

removed blender cruft in worm-with-muscles.png
author Robert McIntyre <rlm@mit.edu>
date Wed, 15 Feb 2012 07:12:03 -0700
parents c39b8b29a79e
children 4c07724c4f0a
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@273 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@273 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@273 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@277 178 To the worm, I add a two new nodes which describe a single muscle.
rlm@277 179
rlm@277 180 #+attr_html: width=755
rlm@277 181 #+caption: The node highlighted in orange is the parent node of all muscles in the worm. The arrow highlighted in yellow represents the creature's single muscle, which moves the top segment. The other nodes which are not highlighted are joints, eyes, and ears.
rlm@277 182 [[../images/worm-with-muscle.png]]
rlm@277 183
rlm@277 184
rlm@277 185
rlm@261 186 #+begin_src clojure
rlm@261 187 (defn test-movement
rlm@261 188 ([] (test-movement false))
rlm@261 189 ([record?]
rlm@261 190 (let [creature (doto (worm) (body!))
rlm@261 191
rlm@261 192 muscle-exertion (atom 0)
rlm@261 193 muscles (movement! creature)
rlm@261 194 muscle-display (view-movement)]
rlm@261 195 (.setMass
rlm@261 196 (.getControl (.getChild creature "worm-11") RigidBodyControl)
rlm@261 197 (float 0))
rlm@261 198 (world
rlm@261 199 (nodify [creature (floor)])
rlm@261 200 (merge standard-debug-controls
rlm@261 201 {"key-h"
rlm@261 202 (fn [_ value]
rlm@261 203 (if value
rlm@261 204 (swap! muscle-exertion (partial + 20))))
rlm@261 205 "key-n"
rlm@261 206 (fn [_ value]
rlm@261 207 (if value
rlm@261 208 (swap! muscle-exertion (fn [v] (- v 20)))))})
rlm@261 209 (fn [world]
rlm@261 210 (if record?
rlm@261 211 (Capture/captureVideo
rlm@261 212 world
rlm@261 213 (File. "/home/r/proj/cortex/render/worm-muscles/main-view")))
rlm@261 214 (light-up-everything world)
rlm@261 215 (enable-debug world)
rlm@261 216 (.setTimer world (RatchetTimer. 60))
rlm@261 217 (set-gravity world (Vector3f. 0 0 0))
rlm@261 218 (.setLocation (.getCamera world)
rlm@261 219 (Vector3f. -4.912815, 2.004171, 0.15710819))
rlm@261 220 (.setRotation (.getCamera world)
rlm@261 221 (Quaternion. 0.13828252, 0.65516764,
rlm@261 222 -0.12370994, 0.7323449))
rlm@261 223
rlm@261 224 (comment
rlm@261 225 (com.aurellem.capture.Capture/captureVideo
rlm@261 226 world (file-str "/home/r/proj/ai-videos/hand"))))
rlm@261 227 (fn [world tpf]
rlm@261 228 (muscle-display
rlm@261 229 (map #(% @muscle-exertion) muscles)
rlm@261 230 (if record?
rlm@261 231 (File. "/home/r/proj/cortex/render/worm-muscles/muscles"))))))))
rlm@261 232 #+end_src
rlm@261 233
rlm@261 234 * Video Demonstration
rlm@261 235
rlm@261 236 #+begin_html
rlm@261 237 <div class="figure">
rlm@261 238 <center>
rlm@261 239 <video controls="controls" width="550">
rlm@261 240 <source src="../video/worm-muscles.ogg" type="video/ogg"
rlm@261 241 preload="none" poster="../images/aurellem-1280x480.png" />
rlm@261 242 </video>
rlm@261 243 </center>
rlm@261 244 <p>The worm is now able to move. The bar in the lower right displays
rlm@261 245 the power output of the muscle . Each jump causes 20 more motor neurons to
rlm@261 246 be recruited. Notice that the power output increases non-linearly
rlm@261 247 with motror neuron recruitement, similiar to a human muscle.</p>
rlm@261 248 </div>
rlm@261 249 #+end_html
rlm@261 250
rlm@261 251
rlm@261 252 ** Making the Worm Muscles Video
rlm@261 253 #+name: magick7
rlm@261 254 #+begin_src clojure
rlm@261 255 (ns cortex.video.magick7
rlm@261 256 (:import java.io.File)
rlm@261 257 (:use clojure.contrib.shell-out))
rlm@261 258
rlm@261 259 (defn images [path]
rlm@261 260 (sort (rest (file-seq (File. path)))))
rlm@261 261
rlm@261 262 (def base "/home/r/proj/cortex/render/worm-muscles/")
rlm@261 263
rlm@261 264 (defn pics [file]
rlm@261 265 (images (str base file)))
rlm@261 266
rlm@261 267 (defn combine-images []
rlm@261 268 (let [main-view (pics "main-view")
rlm@261 269 muscles (pics "muscles/0")
rlm@261 270 targets (map
rlm@261 271 #(File. (str base "out/" (format "%07d.png" %)))
rlm@261 272 (range 0 (count main-view)))]
rlm@261 273 (dorun
rlm@261 274 (pmap
rlm@261 275 (comp
rlm@261 276 (fn [[ main-view muscles target]]
rlm@261 277 (println target)
rlm@261 278 (sh "convert"
rlm@261 279 main-view
rlm@261 280 muscles "-geometry" "+320+440" "-composite"
rlm@261 281 target))
rlm@261 282 (fn [& args] (map #(.getCanonicalPath %) args)))
rlm@261 283 main-view muscles targets))))
rlm@261 284 #+end_src
rlm@261 285
rlm@261 286 #+begin_src sh :results silent
rlm@261 287 cd ~/proj/cortex/render/worm-muscles
rlm@261 288 ffmpeg -r 60 -i out/%07d.png -b:v 9000k -c:v libtheora worm-muscles.ogg
rlm@261 289 #+end_src
rlm@158 290
rlm@260 291 * Headers
rlm@260 292 #+name: muscle-header
rlm@260 293 #+begin_src clojure
rlm@260 294 (ns cortex.movement
rlm@260 295 "Give simulated creatures defined in special blender files the power
rlm@260 296 to move around in a simulated environment."
rlm@260 297 {:author "Robert McIntyre"}
rlm@260 298 (:use (cortex world util sense body))
rlm@260 299 (:use clojure.contrib.def)
rlm@260 300 (:import java.awt.image.BufferedImage)
rlm@260 301 (:import com.jme3.scene.Node)
rlm@260 302 (:import com.jme3.math.Vector3f)
rlm@260 303 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@260 304 #+end_src
rlm@260 305
rlm@261 306 #+name: test-header
rlm@261 307 #+begin_src clojure
rlm@261 308 (ns cortex.test.movement
rlm@261 309 (:use (cortex world util sense body movement))
rlm@261 310 (:use cortex.test.body)
rlm@261 311 (:use clojure.contrib.def)
rlm@261 312 (:import java.io.File)
rlm@261 313 (:import java.awt.image.BufferedImage)
rlm@261 314 (:import com.jme3.scene.Node)
rlm@261 315 (:import com.jme3.math.Vector3f)
rlm@261 316 (:import (com.aurellem.capture Capture RatchetTimer))
rlm@261 317 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@158 318
rlm@261 319 (cortex.import/mega-import-jme3)
rlm@261 320 #+end_src
rlm@261 321
rlm@261 322 * Source Listing
rlm@261 323 - [[../src/cortex/movement.clj][cortex.movement]]
rlm@261 324 - [[../src/cortex/test/movement.clj][cortex.test.movement]]
rlm@261 325 - [[../src/cortex/video/magick7.clj][cortex.video.magick7]]
rlm@261 326 #+html: <ul> <li> <a href="../org/movement.org">This org file</a> </li> </ul>
rlm@261 327 - [[http://hg.bortreb.com ][source-repository]]
rlm@158 328
rlm@158 329 * COMMENT code generation
rlm@158 330 #+begin_src clojure :tangle ../src/cortex/movement.clj
rlm@261 331 <<muscle-header>>
rlm@261 332 <<muscle-meta-data>>
rlm@261 333 <<muscle-kernel>>
rlm@261 334 <<visualization>>
rlm@158 335 #+end_src
rlm@261 336
rlm@261 337 #+begin_src clojure :tangle ../src/cortex/test/movement.clj
rlm@261 338 <<test-header>>
rlm@261 339 <<test-movement>>
rlm@261 340 #+end_src
rlm@261 341
rlm@261 342 #+begin_src clojure :tangle ../src/cortex/video/magick7.clj
rlm@261 343 <<magick7>>
rlm@261 344 #+end_src