rlm@157
|
1 #+title: The Sense of Proprioception
|
rlm@157
|
2 #+author: Robert McIntyre
|
rlm@157
|
3 #+email: rlm@mit.edu
|
rlm@157
|
4 #+description: proprioception for simulated creatures
|
rlm@157
|
5 #+keywords: simulation, jMonkeyEngine3, clojure
|
rlm@157
|
6 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@157
|
7 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@157
|
8
|
rlm@257
|
9 * Proprioception
|
rlm@257
|
10
|
rlm@257
|
11 Close your eyes, and touch your nose with your right index finger. How
|
rlm@257
|
12 did you do it? You could not see your hand, and neither your hand nor
|
rlm@257
|
13 your nose could use the sense of touch to guide the path of your hand.
|
rlm@257
|
14 There are no sound cues, and Taste and Smell certainly don't provide
|
rlm@257
|
15 any help. You know where your hand is without your other senses
|
rlm@257
|
16 because of Proprioception.
|
rlm@257
|
17
|
rlm@257
|
18 Humans can sometimes loose this sense through viral infections or
|
rlm@257
|
19 damage to the spinal cord or brain, and when they do, they loose the
|
rlm@257
|
20 ability to control their own bodies without looking directly at the
|
rlm@257
|
21 parts they want to move. In [[http://en.wikipedia.org/wiki/The_Man_Who_Mistook_His_Wife_for_a_Hat][The Man Who Mistook His Wife for a Hat]],
|
rlm@257
|
22 a woman named Christina looses this sense and has to learn how to move
|
rlm@257
|
23 by carefully watching her arms and legs. She describes proprioception
|
rlm@257
|
24 as the "eyes of the body, the way the body sees itself".
|
rlm@257
|
25
|
rlm@257
|
26 Proprioception in humans is mediated by [[http://en.wikipedia.org/wiki/Articular_capsule][joint capsules]], [[http://en.wikipedia.org/wiki/Muscle_spindle][muscle
|
rlm@257
|
27 spindles]], and the [[http://en.wikipedia.org/wiki/Golgi_tendon_organ][Golgi tendon organs]]. These measure the relative
|
rlm@257
|
28 positions of each pody part by monitoring muscle strain and length.
|
rlm@257
|
29
|
rlm@257
|
30 It's clear that this is a vital sense for fulid, graceful
|
rlm@257
|
31 movement. It's also particurally easy to implement in jMonkeyEngine.
|
rlm@257
|
32
|
rlm@257
|
33 My simulated proprioception calculates the relative angles of each
|
rlm@257
|
34 joint from the rest position defined in the blender file. This
|
rlm@257
|
35 simulates the muscle-spindles and joint capsules. I will deal with
|
rlm@257
|
36 Golgi tendon organs, which calculate muscle strain, in the [[./movement.org][next post]].
|
rlm@257
|
37
|
rlm@257
|
38 * Helper Functions
|
rlm@257
|
39
|
rlm@257
|
40 #+name: helpers
|
rlm@157
|
41 #+begin_src clojure
|
rlm@257
|
42 (in-ns 'cortex.proprioception)
|
rlm@157
|
43
|
rlm@173
|
44 (defn right-handed?
|
rlm@173
|
45 "true iff the three vectors form a right handed coordinate
|
rlm@257
|
46 system. The three vectors do not have to be normalized or
|
rlm@257
|
47 orthogonal."
|
rlm@173
|
48 [vec1 vec2 vec3]
|
rlm@157
|
49 (< 0 (.dot (.cross vec1 vec2) vec3)))
|
rlm@157
|
50
|
rlm@173
|
51 (defn absolute-angle
|
rlm@173
|
52 "The angle between 'vec1 and 'vec2. Positive if the angle to get
|
rlm@173
|
53 from 'vec1 to 'vec2 is counterclockwise around 'axis, and negative
|
rlm@173
|
54 otherwise."
|
rlm@173
|
55 [vec1 vec2 axis]
|
rlm@157
|
56 (let [angle (.angleBetween vec1 vec2)]
|
rlm@157
|
57 (if (right-handed? vec1 vec2 axis)
|
rlm@157
|
58 angle (- (* 2 Math/PI) angle))))
|
rlm@257
|
59 #+end_src
|
rlm@157
|
60
|
rlm@258
|
61 * Proprioception Kernel
|
rlm@258
|
62
|
rlm@257
|
63 #+name: proprioception
|
rlm@257
|
64 #+begin_src clojure
|
rlm@257
|
65 (defn proprioception-kernel
|
rlm@173
|
66 "Returns a function which returns proprioceptive sensory data when
|
rlm@173
|
67 called inside a running simulation."
|
rlm@173
|
68 [#^Node parts #^Node joint]
|
rlm@157
|
69 (let [[obj-a obj-b] (joint-targets parts joint)
|
rlm@157
|
70 joint-rot (.getWorldRotation joint)
|
rlm@157
|
71 x0 (.mult joint-rot Vector3f/UNIT_X)
|
rlm@157
|
72 y0 (.mult joint-rot Vector3f/UNIT_Y)
|
rlm@157
|
73 z0 (.mult joint-rot Vector3f/UNIT_Z)]
|
rlm@157
|
74 (println-repl "x:" x0)
|
rlm@157
|
75 (println-repl "y:" y0)
|
rlm@157
|
76 (println-repl "z:" z0)
|
rlm@157
|
77 (println-repl "init-a:" (.getWorldRotation obj-a))
|
rlm@157
|
78 (println-repl "init-b:" (.getWorldRotation obj-b))
|
rlm@157
|
79
|
rlm@157
|
80 (fn []
|
rlm@157
|
81 (let [rot-a (.clone (.getWorldRotation obj-a))
|
rlm@157
|
82 rot-b (.clone (.getWorldRotation obj-b))
|
rlm@157
|
83 x (.mult rot-a x0)
|
rlm@157
|
84 y (.mult rot-a y0)
|
rlm@157
|
85 z (.mult rot-a z0)
|
rlm@157
|
86
|
rlm@157
|
87 X (.mult rot-b x0)
|
rlm@157
|
88 Y (.mult rot-b y0)
|
rlm@157
|
89 Z (.mult rot-b z0)
|
rlm@157
|
90 heading (Math/atan2 (.dot X z) (.dot X x))
|
rlm@157
|
91 pitch (Math/atan2 (.dot X y) (.dot X x))
|
rlm@157
|
92
|
rlm@157
|
93 ;; rotate x-vector back to origin
|
rlm@157
|
94 reverse
|
rlm@157
|
95 (doto (Quaternion.)
|
rlm@157
|
96 (.fromAngleAxis
|
rlm@157
|
97 (.angleBetween X x)
|
rlm@157
|
98 (let [cross (.normalize (.cross X x))]
|
rlm@157
|
99 (if (= 0 (.length cross)) y cross))))
|
rlm@157
|
100 roll (absolute-angle (.mult reverse Y) y x)]
|
rlm@157
|
101 [heading pitch roll]))))
|
rlm@157
|
102
|
rlm@173
|
103 (defn proprioception!
|
rlm@173
|
104 "Endow the creature with the sense of proprioception. Returns a
|
rlm@173
|
105 sequence of functions, one for each child of the \"joints\" node in
|
rlm@173
|
106 the creature, which each report proprioceptive information about
|
rlm@173
|
107 that joint."
|
rlm@157
|
108 [#^Node creature]
|
rlm@157
|
109 ;; extract the body's joints
|
rlm@257
|
110 (let [senses (map (partial proprioception-kernel creature)
|
rlm@173
|
111 (joints creature))]
|
rlm@157
|
112 (fn []
|
rlm@157
|
113 (map #(%) senses))))
|
rlm@257
|
114 #+end_src
|
rlm@175
|
115
|
rlm@258
|
116 * Visualizing Proprioception
|
rlm@258
|
117
|
rlm@257
|
118 #+name: visualize
|
rlm@257
|
119 #+begin_src clojure
|
rlm@257
|
120 (in-ns 'cortex.proprioception)
|
rlm@175
|
121
|
rlm@175
|
122 (defn draw-sprite [image sprite x y color ]
|
rlm@175
|
123 (dorun
|
rlm@175
|
124 (for [[u v] sprite]
|
rlm@175
|
125 (.setRGB image (+ u x) (+ v y) color))))
|
rlm@175
|
126
|
rlm@175
|
127 (defn view-angle
|
rlm@175
|
128 "create a debug view of an angle"
|
rlm@175
|
129 [color]
|
rlm@175
|
130 (let [image (BufferedImage. 50 50 BufferedImage/TYPE_INT_RGB)
|
rlm@175
|
131 previous (atom [25 25])
|
rlm@175
|
132 sprite [[0 0] [0 1]
|
rlm@175
|
133 [0 -1] [-1 0] [1 0]]]
|
rlm@175
|
134 (fn [angle]
|
rlm@175
|
135 (let [angle (float angle)]
|
rlm@175
|
136 (let [position
|
rlm@175
|
137 [(+ 25 (int (* 20 (Math/cos angle))))
|
rlm@175
|
138 (+ 25 (int (* -20 (Math/sin angle))))]]
|
rlm@175
|
139 (draw-sprite image sprite (@previous 0) (@previous 1) 0x000000)
|
rlm@175
|
140 (draw-sprite image sprite (position 0) (position 1) color)
|
rlm@175
|
141 (reset! previous position))
|
rlm@175
|
142 image))))
|
rlm@175
|
143
|
rlm@190
|
144
|
rlm@190
|
145 (defn proprioception-display-kernel
|
rlm@190
|
146 "Display proprioception angles in a BufferedImage"
|
rlm@190
|
147 [[h p r]]
|
rlm@190
|
148 (let [image (BufferedImage. 50 50 BufferedImage/TYPE_INT_RGB)
|
rlm@190
|
149 previous-heading (atom [25 25])
|
rlm@190
|
150 previous-pitch (atom [25 25])
|
rlm@190
|
151 previous-roll (atom [25 25])
|
rlm@190
|
152
|
rlm@190
|
153 heading-sprite [[0 0] [0 1] [0 -1] [-1 0] [1 0]]
|
rlm@190
|
154 pitch-sprite [[0 0] [0 1] [0 -1] [-1 0] [1 0]]
|
rlm@190
|
155 roll-sprite [[0 0] [0 1] [0 -1] [-1 0] [1 0]]
|
rlm@190
|
156 draw-angle
|
rlm@190
|
157 (fn [angle sprite previous color]
|
rlm@190
|
158 (let [angle (float angle)]
|
rlm@190
|
159 (let [position
|
rlm@190
|
160 [(+ 25 (int (* 20 (Math/cos angle))))
|
rlm@190
|
161 (+ 25 (int (* -20 (Math/sin angle))))]]
|
rlm@190
|
162 (draw-sprite image sprite (@previous 0) (@previous 1) 0x000000)
|
rlm@190
|
163 (draw-sprite image sprite (position 0) (position 1) color)
|
rlm@190
|
164 (reset! previous position))
|
rlm@190
|
165 image))]
|
rlm@190
|
166 (dorun (map draw-angle
|
rlm@190
|
167 [h p r]
|
rlm@190
|
168 [heading-sprite pitch-sprite roll-sprite]
|
rlm@190
|
169 [previous-heading previous-pitch previous-roll]
|
rlm@190
|
170 [0xFF0000 0x00FF00 0xFFFFFF]))
|
rlm@190
|
171 image))
|
rlm@190
|
172
|
rlm@190
|
173 (defn view-proprioception
|
rlm@190
|
174 "Creates a function which accepts a list of proprioceptive data and
|
rlm@190
|
175 display each element of the list to the screen as an image."
|
rlm@175
|
176 []
|
rlm@190
|
177 (view-sense proprioception-display-kernel))
|
rlm@257
|
178 #+end_src
|
rlm@175
|
179
|
rlm@258
|
180 * Demonstration of Proprioception
|
rlm@157
|
181
|
rlm@206
|
182 #+name: test-body
|
rlm@206
|
183 #+begin_src clojure
|
rlm@206
|
184 (defn test-proprioception
|
rlm@206
|
185 "Testing proprioception:
|
rlm@206
|
186 You should see two foating bars, and a printout of pitch, yaw, and
|
rlm@206
|
187 roll. Pressing key-r/key-t should move the blue bar up and down and
|
rlm@206
|
188 change only the value of pitch. key-f/key-g moves it side to side
|
rlm@206
|
189 and changes yaw. key-v/key-b will spin the blue segment clockwise
|
rlm@206
|
190 and counterclockwise, and only affect roll."
|
rlm@206
|
191 []
|
rlm@206
|
192 (let [hand (box 0.2 1 0.2 :position (Vector3f. 0 0 0)
|
rlm@206
|
193 :mass 0 :color ColorRGBA/Green :name "hand")
|
rlm@206
|
194 finger (box 0.2 1 0.2 :position (Vector3f. 0 2.4 0)
|
rlm@206
|
195 :mass 1 :color ColorRGBA/Red :name "finger")
|
rlm@206
|
196 joint-node (box 0.1 0.05 0.05 :color ColorRGBA/Yellow
|
rlm@206
|
197 :position (Vector3f. 0 1.2 0)
|
rlm@206
|
198 :rotation (doto (Quaternion.)
|
rlm@206
|
199 (.fromAngleAxis
|
rlm@206
|
200 (/ Math/PI 2)
|
rlm@206
|
201 (Vector3f. 0 0 1)))
|
rlm@206
|
202 :physical? false)
|
rlm@206
|
203 joint (join-at-point hand finger (Vector3f. 0 1.2 0 ))
|
rlm@206
|
204 creature (nodify [hand finger joint-node])
|
rlm@206
|
205 finger-control (.getControl finger RigidBodyControl)
|
rlm@206
|
206 hand-control (.getControl hand RigidBodyControl)]
|
rlm@206
|
207
|
rlm@206
|
208
|
rlm@206
|
209 (let
|
rlm@206
|
210 ;; *******************************************
|
rlm@206
|
211
|
rlm@206
|
212 [floor (box 10 10 10 :position (Vector3f. 0 -15 0)
|
rlm@206
|
213 :mass 0 :color ColorRGBA/Gray)
|
rlm@206
|
214
|
rlm@206
|
215 root (nodify [creature floor])
|
rlm@206
|
216 prop (joint-proprioception creature joint-node)
|
rlm@206
|
217 prop-view (proprioception-debug-window)
|
rlm@206
|
218
|
rlm@206
|
219 controls
|
rlm@206
|
220 (merge standard-debug-controls
|
rlm@206
|
221 {"key-o"
|
rlm@206
|
222 (fn [_ _] (.setEnabled finger-control true))
|
rlm@206
|
223 "key-p"
|
rlm@206
|
224 (fn [_ _] (.setEnabled finger-control false))
|
rlm@206
|
225 "key-k"
|
rlm@206
|
226 (fn [_ _] (.setEnabled hand-control true))
|
rlm@206
|
227 "key-l"
|
rlm@206
|
228 (fn [_ _] (.setEnabled hand-control false))
|
rlm@206
|
229 "key-i"
|
rlm@206
|
230 (fn [world _] (set-gravity world (Vector3f. 0 0 0)))
|
rlm@206
|
231 "key-period"
|
rlm@206
|
232 (fn [world _]
|
rlm@206
|
233 (.setEnabled finger-control false)
|
rlm@206
|
234 (.setEnabled hand-control false)
|
rlm@206
|
235 (.rotate creature (doto (Quaternion.)
|
rlm@206
|
236 (.fromAngleAxis
|
rlm@206
|
237 (float (/ Math/PI 15))
|
rlm@206
|
238 (Vector3f. 0 0 -1))))
|
rlm@206
|
239
|
rlm@206
|
240 (.setEnabled finger-control true)
|
rlm@206
|
241 (.setEnabled hand-control true)
|
rlm@206
|
242 (set-gravity world (Vector3f. 0 0 0))
|
rlm@206
|
243 )
|
rlm@206
|
244
|
rlm@206
|
245
|
rlm@206
|
246 }
|
rlm@206
|
247 )
|
rlm@206
|
248
|
rlm@206
|
249 ]
|
rlm@206
|
250 (comment
|
rlm@206
|
251 (.setCollisionGroup
|
rlm@206
|
252 (.getControl hand RigidBodyControl)
|
rlm@206
|
253 PhysicsCollisionObject/COLLISION_GROUP_NONE)
|
rlm@206
|
254 )
|
rlm@206
|
255 (apply
|
rlm@206
|
256 world
|
rlm@206
|
257 (with-movement
|
rlm@206
|
258 hand
|
rlm@206
|
259 ["key-y" "key-u" "key-h" "key-j" "key-n" "key-m"]
|
rlm@206
|
260 [10 10 10 10 1 1]
|
rlm@206
|
261 (with-movement
|
rlm@206
|
262 finger
|
rlm@206
|
263 ["key-r" "key-t" "key-f" "key-g" "key-v" "key-b"]
|
rlm@206
|
264 [1 1 10 10 10 10]
|
rlm@206
|
265 [root
|
rlm@206
|
266 controls
|
rlm@206
|
267 (fn [world]
|
rlm@206
|
268 (.setTimer world (com.aurellem.capture.RatchetTimer. 60))
|
rlm@206
|
269 (set-gravity world (Vector3f. 0 0 0))
|
rlm@206
|
270 (light-up-everything world))
|
rlm@206
|
271 (fn [_ _] (prop-view (list (prop))))]))))))
|
rlm@258
|
272 #+end_src
|
rlm@206
|
273
|
rlm@258
|
274 * Headers
|
rlm@258
|
275 #+name: proprioception-header
|
rlm@258
|
276 #+begin_src clojure
|
rlm@258
|
277 (ns cortex.proprioception
|
rlm@258
|
278 "Simulate the sense of proprioception (ability to detect the
|
rlm@258
|
279 relative positions of body parts with repsect to other body parts)
|
rlm@258
|
280 in jMonkeyEngine3. Reads specially prepared blender files to
|
rlm@258
|
281 automatically generate proprioceptive senses."
|
rlm@258
|
282 (:use (cortex world util sense body))
|
rlm@258
|
283 (:use clojure.contrib.def)
|
rlm@258
|
284 (:import com.jme3.scene.Node)
|
rlm@258
|
285 (:import java.awt.image.BufferedImage)
|
rlm@258
|
286 (:import (com.jme3.math Vector3f Quaternion)))
|
rlm@206
|
287 #+end_src
|
rlm@206
|
288
|
rlm@206
|
289
|
rlm@157
|
290 * COMMENT generate source
|
rlm@157
|
291 #+begin_src clojure :tangle ../src/cortex/proprioception.clj
|
rlm@257
|
292 <<proprioception-header>>
|
rlm@257
|
293 <<helpers>>
|
rlm@157
|
294 <<proprioception>>
|
rlm@257
|
295 <<visualize>>
|
rlm@157
|
296 #+end_src
|