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
|