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