annotate org/movement.org @ 279:d8ff1a293b8c

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