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
|