annotate org/movement.org @ 513:4c4d45f6f30b

accept/reject changes
author Robert McIntyre <rlm@mit.edu>
date Sun, 30 Mar 2014 10:41:18 -0400
parents 5205535237fb
children
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@306 12 Surprisingly enough, terrestrial 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@278 17 In humans, muscles are composed of muscle fibers which can contract to
rlm@278 18 exert force. The muscle fibers which compose a muscle are partitioned
rlm@278 19 into discrete groups which are each controlled by a single alpha motor
rlm@306 20 neuron. A single alpha motor neuron might control as little as three
rlm@278 21 or as many as one thousand muscle fibers. When the alpha motor neuron
rlm@278 22 is engaged by the spinal cord, it activates all of the muscle fibers
rlm@278 23 to which it is attached. The spinal cord generally engages the alpha
rlm@278 24 motor neurons which control few muscle fibers before the motor neurons
rlm@306 25 which control many muscle fibers. This recruitment strategy allows
rlm@306 26 for precise movements at low strength. The collection of all motor
rlm@278 27 neurons that control a muscle is called the motor pool. The brain
rlm@278 28 essentially says "activate 30% of the motor pool" and the spinal cord
rlm@306 29 recruits motor neurons until 30% are activated. Since the
rlm@278 30 distribution of power among motor neurons is unequal and recruitment
rlm@278 31 goes from weakest to strongest, the first 30% of the motor pool might
rlm@278 32 be 5% of the strength of the muscle.
rlm@260 33
rlm@306 34 My simulated muscles follow a similar design: Each muscle is defined
rlm@267 35 by a 1-D array of numbers (the "motor pool"). Each entry in the array
rlm@306 36 represents a motor neuron which controls a number of muscle fibers
rlm@278 37 equal to the value of the entry. Each muscle has a scalar strength
rlm@278 38 factor which determines the total force the muscle can exert when all
rlm@278 39 motor neurons are activated. The effector function for a muscle takes
rlm@278 40 a number to index into the motor pool, and then "activates" all the
rlm@278 41 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@306 45 muscles, which are capable of more precise movements when exerting
rlm@306 46 less force. Or, the motor pool can simulate more exotic recruitment
rlm@306 47 strategies 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@317 66 (def
rlm@317 67 ^{:doc "Return the children of the creature's \"muscles\" node."
rlm@317 68 :arglists '([creature])}
rlm@180 69 muscles
rlm@317 70 (sense-nodes "muscles"))
rlm@317 71
rlm@158 72
rlm@260 73 (defn muscle-profile-image
rlm@260 74 "Get the muscle-profile image from the node's blender meta-data."
rlm@260 75 [#^Node muscle]
rlm@260 76 (if-let [image (meta-data muscle "muscle")]
rlm@260 77 (load-image image)))
rlm@260 78
rlm@260 79 (defn muscle-strength
rlm@260 80 "Return the strength of this muscle, or 1 if it is not defined."
rlm@260 81 [#^Node muscle]
rlm@260 82 (if-let [strength (meta-data muscle "strength")]
rlm@260 83 strength 1))
rlm@260 84
rlm@260 85 (defn motor-pool
rlm@260 86 "Return a vector where each entry is the strength of the \"motor
rlm@260 87 neuron\" at that part in the muscle."
rlm@260 88 [#^Node muscle]
rlm@260 89 (let [profile (muscle-profile-image muscle)]
rlm@260 90 (vec
rlm@260 91 (let [width (.getWidth profile)]
rlm@260 92 (for [x (range width)]
rlm@260 93 (- 255
rlm@260 94 (bit-and
rlm@260 95 0x0000FF
rlm@260 96 (.getRGB profile x 0))))))))
rlm@260 97 #+end_src
rlm@260 98
rlm@273 99 Of note here is =motor-pool= which interprets the muscle-profile
rlm@260 100 image in a way that allows me to use gradients between white and red,
rlm@260 101 instead of shades of gray as I've been using for all the other
rlm@260 102 senses. This is purely an aesthetic touch.
rlm@260 103
rlm@260 104 * Creating Muscles
rlm@261 105 #+name: muscle-kernel
rlm@260 106 #+begin_src clojure
rlm@261 107 (in-ns 'cortex.movement)
rlm@261 108
rlm@260 109 (defn movement-kernel
rlm@180 110 "Returns a function which when called with a integer value inside a
rlm@191 111 running simulation will cause movement in the creature according
rlm@191 112 to the muscle's position and strength profile. Each function
rlm@191 113 returns the amount of force applied / max force."
rlm@260 114 [#^Node creature #^Node muscle]
rlm@260 115 (let [target (closest-node creature muscle)
rlm@158 116 axis
rlm@158 117 (.mult (.getWorldRotation muscle) Vector3f/UNIT_Y)
rlm@260 118 strength (muscle-strength muscle)
rlm@260 119
rlm@260 120 pool (motor-pool muscle)
rlm@260 121 pool-integral (reductions + pool)
rlm@296 122 forces
rlm@260 123 (vec (map #(float (* strength (/ % (last pool-integral))))
rlm@260 124 pool-integral))
rlm@158 125 control (.getControl target RigidBodyControl)]
rlm@321 126 ;;(println-repl (.getName target) axis)
rlm@158 127 (fn [n]
rlm@260 128 (let [pool-index (max 0 (min n (dec (count pool))))
rlm@296 129 force (forces 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@306 144 force dependent 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@306 152 Muscle exertion is a percent of a total, so the visualization 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@386 178 * Adding Muscles 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@283 186 #+name: test-movement
rlm@261 187 #+begin_src clojure
rlm@340 188 (in-ns 'cortex.test.movement)
rlm@340 189
rlm@283 190 (defn test-worm-movement
rlm@321 191 "Testing movement:
rlm@321 192 You should see the worm suspended in mid air and a display on the
rlm@321 193 right which shows the current relative power being exerted by the
rlm@321 194 muscle. As you increase muscle strength, the bar should fill with
rlm@321 195 red, and the worm's upper segment should move.
rlm@321 196
rlm@321 197 Keys:
rlm@321 198 h : increase muscle exertion
rlm@321 199 n : decrease muscle exertion"
rlm@283 200 ([] (test-worm-movement false))
rlm@261 201 ([record?]
rlm@261 202 (let [creature (doto (worm) (body!))
rlm@261 203
rlm@261 204 muscle-exertion (atom 0)
rlm@261 205 muscles (movement! creature)
rlm@261 206 muscle-display (view-movement)]
rlm@261 207 (.setMass
rlm@261 208 (.getControl (.getChild creature "worm-11") RigidBodyControl)
rlm@261 209 (float 0))
rlm@261 210 (world
rlm@261 211 (nodify [creature (floor)])
rlm@261 212 (merge standard-debug-controls
rlm@261 213 {"key-h"
rlm@261 214 (fn [_ value]
rlm@261 215 (if value
rlm@261 216 (swap! muscle-exertion (partial + 20))))
rlm@261 217 "key-n"
rlm@261 218 (fn [_ value]
rlm@261 219 (if value
rlm@261 220 (swap! muscle-exertion (fn [v] (- v 20)))))})
rlm@261 221 (fn [world]
rlm@340 222
rlm@340 223 (let [timer (RatchetTimer. 60)]
rlm@340 224 (.setTimer world timer)
rlm@340 225 (display-dilated-time world timer))
rlm@261 226 (if record?
rlm@261 227 (Capture/captureVideo
rlm@261 228 world
rlm@261 229 (File. "/home/r/proj/cortex/render/worm-muscles/main-view")))
rlm@261 230 (light-up-everything world)
rlm@261 231 (enable-debug world)
rlm@261 232 (set-gravity world (Vector3f. 0 0 0))
rlm@261 233 (.setLocation (.getCamera world)
rlm@261 234 (Vector3f. -4.912815, 2.004171, 0.15710819))
rlm@261 235 (.setRotation (.getCamera world)
rlm@261 236 (Quaternion. 0.13828252, 0.65516764,
rlm@278 237 -0.12370994, 0.7323449)))
rlm@261 238 (fn [world tpf]
rlm@261 239 (muscle-display
rlm@261 240 (map #(% @muscle-exertion) muscles)
rlm@261 241 (if record?
rlm@261 242 (File. "/home/r/proj/cortex/render/worm-muscles/muscles"))))))))
rlm@261 243 #+end_src
rlm@261 244
rlm@340 245 #+results: test-movement
rlm@340 246 : #'cortex.test.movement/test-worm-movement
rlm@340 247
rlm@261 248 * Video Demonstration
rlm@261 249
rlm@261 250 #+begin_html
rlm@261 251 <div class="figure">
rlm@261 252 <center>
rlm@261 253 <video controls="controls" width="550">
rlm@261 254 <source src="../video/worm-muscles.ogg" type="video/ogg"
rlm@261 255 preload="none" poster="../images/aurellem-1280x480.png" />
rlm@261 256 </video>
rlm@309 257 <br> <a href="http://youtu.be/8Rp4jEGMDWU"> YouTube </a>
rlm@261 258 </center>
rlm@261 259 <p>The worm is now able to move. The bar in the lower right displays
rlm@261 260 the power output of the muscle . Each jump causes 20 more motor neurons to
rlm@261 261 be recruited. Notice that the power output increases non-linearly
rlm@306 262 with motor neuron recruitment, similar to a human muscle.</p>
rlm@261 263 </div>
rlm@261 264 #+end_html
rlm@261 265
rlm@261 266 ** Making the Worm Muscles Video
rlm@261 267 #+name: magick7
rlm@261 268 #+begin_src clojure
rlm@261 269 (ns cortex.video.magick7
rlm@261 270 (:import java.io.File)
rlm@316 271 (:use clojure.java.shell))
rlm@261 272
rlm@261 273 (defn images [path]
rlm@261 274 (sort (rest (file-seq (File. path)))))
rlm@261 275
rlm@261 276 (def base "/home/r/proj/cortex/render/worm-muscles/")
rlm@261 277
rlm@261 278 (defn pics [file]
rlm@261 279 (images (str base file)))
rlm@261 280
rlm@261 281 (defn combine-images []
rlm@261 282 (let [main-view (pics "main-view")
rlm@261 283 muscles (pics "muscles/0")
rlm@261 284 targets (map
rlm@261 285 #(File. (str base "out/" (format "%07d.png" %)))
rlm@430 286 (range (count main-view)))]
rlm@261 287 (dorun
rlm@261 288 (pmap
rlm@261 289 (comp
rlm@261 290 (fn [[ main-view muscles target]]
rlm@261 291 (println target)
rlm@261 292 (sh "convert"
rlm@261 293 main-view
rlm@261 294 muscles "-geometry" "+320+440" "-composite"
rlm@261 295 target))
rlm@261 296 (fn [& args] (map #(.getCanonicalPath %) args)))
rlm@261 297 main-view muscles targets))))
rlm@261 298 #+end_src
rlm@261 299
rlm@261 300 #+begin_src sh :results silent
rlm@261 301 cd ~/proj/cortex/render/worm-muscles
rlm@261 302 ffmpeg -r 60 -i out/%07d.png -b:v 9000k -c:v libtheora worm-muscles.ogg
rlm@261 303 #+end_src
rlm@158 304
rlm@260 305 * Headers
rlm@260 306 #+name: muscle-header
rlm@260 307 #+begin_src clojure
rlm@260 308 (ns cortex.movement
rlm@260 309 "Give simulated creatures defined in special blender files the power
rlm@260 310 to move around in a simulated environment."
rlm@260 311 {:author "Robert McIntyre"}
rlm@260 312 (:use (cortex world util sense body))
rlm@260 313 (:import java.awt.image.BufferedImage)
rlm@260 314 (:import com.jme3.scene.Node)
rlm@260 315 (:import com.jme3.math.Vector3f)
rlm@260 316 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@260 317 #+end_src
rlm@260 318
rlm@261 319 #+name: test-header
rlm@261 320 #+begin_src clojure
rlm@261 321 (ns cortex.test.movement
rlm@261 322 (:use (cortex world util sense body movement))
rlm@261 323 (:use cortex.test.body)
rlm@261 324 (:import java.io.File)
rlm@261 325 (:import java.awt.image.BufferedImage)
rlm@261 326 (:import com.jme3.scene.Node)
rlm@283 327 (:import (com.jme3.math Quaternion Vector3f))
rlm@341 328 (:import (com.aurellem.capture Capture RatchetTimer IsoTimer))
rlm@261 329 (:import com.jme3.bullet.control.RigidBodyControl))
rlm@261 330 #+end_src
rlm@261 331
rlm@341 332 #+results: test-header
rlm@341 333 : com.jme3.bullet.control.RigidBodyControl
rlm@341 334
rlm@261 335 * Source Listing
rlm@261 336 - [[../src/cortex/movement.clj][cortex.movement]]
rlm@261 337 - [[../src/cortex/test/movement.clj][cortex.test.movement]]
rlm@261 338 - [[../src/cortex/video/magick7.clj][cortex.video.magick7]]
rlm@261 339 #+html: <ul> <li> <a href="../org/movement.org">This org file</a> </li> </ul>
rlm@261 340 - [[http://hg.bortreb.com ][source-repository]]
rlm@158 341
rlm@158 342 * COMMENT code generation
rlm@158 343 #+begin_src clojure :tangle ../src/cortex/movement.clj
rlm@261 344 <<muscle-header>>
rlm@261 345 <<muscle-meta-data>>
rlm@261 346 <<muscle-kernel>>
rlm@261 347 <<visualization>>
rlm@158 348 #+end_src
rlm@261 349
rlm@261 350 #+begin_src clojure :tangle ../src/cortex/test/movement.clj
rlm@261 351 <<test-header>>
rlm@261 352 <<test-movement>>
rlm@261 353 #+end_src
rlm@261 354
rlm@261 355 #+begin_src clojure :tangle ../src/cortex/video/magick7.clj
rlm@261 356 <<magick7>>
rlm@261 357 #+end_src