rlm@23
|
1 #+title: A world for the creatures to live
|
rlm@23
|
2 #+author: Robert McIntyre
|
rlm@23
|
3 #+email: rlm@mit.edu
|
rlm@23
|
4 #+description: Simulating senses for AI research using JMonkeyEngine3
|
rlm@23
|
5 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@23
|
6 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@23
|
7 #+babel: :mkdirp yes :noweb yes :exports both
|
rlm@23
|
8
|
rlm@23
|
9
|
rlm@23
|
10
|
rlm@23
|
11 *** World
|
rlm@23
|
12
|
rlm@23
|
13 It is comvienent to wrap the JME elements that deal with creating a
|
rlm@23
|
14 world, creation of basic objects, and Keyboard input with a nicer
|
rlm@23
|
15 interface (at least for my purposes).
|
rlm@23
|
16
|
rlm@23
|
17 #+srcname: world-inputs
|
rlm@23
|
18 #+begin_src clojure :results silent
|
rlm@23
|
19 (ns cortex.world)
|
rlm@23
|
20 (require 'cortex.import)
|
rlm@23
|
21 (use 'clojure.contrib.def)
|
rlm@23
|
22 (rlm.rlm-commands/help)
|
rlm@23
|
23 (cortex.import/mega-import-jme3)
|
rlm@23
|
24
|
rlm@23
|
25 (defvar *app-settings*
|
rlm@23
|
26 (doto (AppSettings. true)
|
rlm@23
|
27 (.setFullscreen false)
|
rlm@23
|
28 (.setTitle "Aurellem.")
|
rlm@23
|
29 ;; disable 32 bit stuff for now
|
rlm@23
|
30 ;;(.setAudioRenderer "Send")
|
rlm@23
|
31 )
|
rlm@23
|
32 "These settings control how the game is displayed on the screen for
|
rlm@23
|
33 debugging purposes. Use binding forms to change this if desired.
|
rlm@23
|
34 Full-screen mode does not work on some computers.")
|
rlm@23
|
35
|
rlm@23
|
36 (defn asset-manager
|
rlm@23
|
37 "returns a new, configured assetManager" []
|
rlm@23
|
38 (JmeSystem/newAssetManager
|
rlm@23
|
39 (.getResource
|
rlm@23
|
40 (.getContextClassLoader (Thread/currentThread))
|
rlm@23
|
41 "com/jme3/asset/Desktop.cfg")))
|
rlm@23
|
42
|
rlm@23
|
43 (defmacro no-exceptions
|
rlm@23
|
44 "Sweet relief like I never knew."
|
rlm@23
|
45 [& forms]
|
rlm@23
|
46 `(try ~@forms (catch Exception e# (.printStackTrace e#))))
|
rlm@23
|
47
|
rlm@23
|
48 (defn thread-exception-removal []
|
rlm@23
|
49 (println "removing exceptions from " (Thread/currentThread))
|
rlm@23
|
50 (.setUncaughtExceptionHandler
|
rlm@23
|
51 (Thread/currentThread)
|
rlm@23
|
52 (proxy [Thread$UncaughtExceptionHandler] []
|
rlm@23
|
53 (uncaughtException
|
rlm@23
|
54 [thread thrown]
|
rlm@23
|
55 (println "uncaught-exception thrown in " thread)
|
rlm@23
|
56 (println (.getMessage thrown))))))
|
rlm@23
|
57
|
rlm@23
|
58 (def println-repl (bound-fn [& args] (apply println args)))
|
rlm@23
|
59
|
rlm@23
|
60 (use '[pokemon [lpsolve :only [constant-map]]])
|
rlm@23
|
61
|
rlm@23
|
62 (defn no-op [& _])
|
rlm@23
|
63
|
rlm@23
|
64 (defn all-keys
|
rlm@23
|
65 "Construct a map of strings representing all the manual inputs from
|
rlm@23
|
66 either the keyboard or mouse."
|
rlm@23
|
67 []
|
rlm@23
|
68 (let [inputs (constant-map KeyInput)]
|
rlm@23
|
69 (assoc
|
rlm@23
|
70 (zipmap (map (fn [field]
|
rlm@23
|
71 (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs))
|
rlm@23
|
72 (map (fn [val] (KeyTrigger. val)) (keys inputs)))
|
rlm@23
|
73 ;;explicitly add mouse controls
|
rlm@23
|
74 "mouse-left" (MouseButtonTrigger. 0)
|
rlm@23
|
75 "mouse-middle" (MouseButtonTrigger. 2)
|
rlm@23
|
76 "mouse-right" (MouseButtonTrigger. 1))))
|
rlm@23
|
77
|
rlm@23
|
78 (defn initialize-inputs
|
rlm@23
|
79 "more java-interop cruft to establish keybindings for a particular virtual world"
|
rlm@23
|
80 [game input-manager key-map]
|
rlm@23
|
81 (doall (map (fn [[name trigger]]
|
rlm@23
|
82 (.addMapping ^InputManager input-manager
|
rlm@23
|
83 name (into-array (class trigger) [trigger]))) key-map))
|
rlm@23
|
84 (doall (map (fn [name]
|
rlm@23
|
85 (.addListener ^InputManager input-manager game
|
rlm@23
|
86 (into-array String [name]))) (keys key-map))))
|
rlm@23
|
87
|
rlm@23
|
88 #+end_src
|
rlm@23
|
89
|
rlm@23
|
90 These functions are all for debug controlling of the world through
|
rlm@23
|
91 keyboard and mouse.
|
rlm@23
|
92
|
rlm@23
|
93 We reuse =constant-map= from =pokemon.lpsolve= to get the numerical
|
rlm@23
|
94 values for all the keys defined in the =KeyInput= class. The
|
rlm@23
|
95 documentation for =constant-map= is:
|
rlm@23
|
96
|
rlm@23
|
97 #+begin_src clojure :results output
|
rlm@23
|
98 (doc pokemon.lpsolve/constant-map)
|
rlm@23
|
99 #+end_src
|
rlm@23
|
100
|
rlm@23
|
101 #+results:
|
rlm@23
|
102 : -------------------------
|
rlm@23
|
103 : pokemon.lpsolve/constant-map
|
rlm@23
|
104 : ([class])
|
rlm@23
|
105 : Takes a class and creates a map of the static constant integer
|
rlm@23
|
106 : fields with their names. This helps with C wrappers where they have
|
rlm@23
|
107 : just defined a bunch of integer constants instead of enums
|
rlm@23
|
108
|
rlm@23
|
109
|
rlm@23
|
110 Then, =all-keys= converts the constant names like =KEY_J= to the more
|
rlm@23
|
111 clojure-like =key-j=, and returns a map from these keys to
|
rlm@23
|
112 jMonkeyEngine KeyTrigger objects, the use of which will soon become
|
rlm@23
|
113 apparent. =all-keys= also adds the three mouse button controls to the
|
rlm@23
|
114 map.
|
rlm@23
|
115
|
rlm@23
|
116 #+srcname: world
|
rlm@23
|
117 #+begin_src clojure :results silent
|
rlm@23
|
118 (in-ns 'cortex.world)
|
rlm@23
|
119
|
rlm@23
|
120 (defn traverse
|
rlm@23
|
121 "apply f to every non-node, deeply"
|
rlm@23
|
122 [f node]
|
rlm@23
|
123 (if (isa? (class node) Node)
|
rlm@23
|
124 (dorun (map (partial traverse f) (.getChildren node)))
|
rlm@23
|
125 (f node)))
|
rlm@23
|
126
|
rlm@23
|
127 (def gravity (Vector3f. 0 -9.81 0))
|
rlm@23
|
128
|
rlm@23
|
129 (defn world
|
rlm@23
|
130 [root-node key-map setup-fn update-fn]
|
rlm@23
|
131 (let [physics-manager (BulletAppState.)
|
rlm@23
|
132 shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256))
|
rlm@23
|
133 ;;maybe use a better shadow renderer someday!
|
rlm@23
|
134 ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1)
|
rlm@23
|
135 ]
|
rlm@23
|
136 (doto
|
rlm@23
|
137 (proxy [SimpleApplication ActionListener] []
|
rlm@23
|
138 (simpleInitApp
|
rlm@23
|
139 []
|
rlm@23
|
140 (no-exceptions
|
rlm@23
|
141 (.setTimer this (IsoTimer. 60))
|
rlm@23
|
142 ;; Create key-map.
|
rlm@23
|
143 (.setFrustumFar (.getCamera this) 300)
|
rlm@23
|
144 (initialize-inputs this (.getInputManager this) (all-keys))
|
rlm@23
|
145 ;; Don't take control of the mouse
|
rlm@23
|
146 (org.lwjgl.input.Mouse/setGrabbed false)
|
rlm@23
|
147 ;; add all objects to the world
|
rlm@23
|
148 (.attachChild (.getRootNode this) root-node)
|
rlm@23
|
149 ;; enable physics
|
rlm@23
|
150 ;; add a physics manager
|
rlm@23
|
151 (.attach (.getStateManager this) physics-manager)
|
rlm@23
|
152 (.setGravity (.getPhysicsSpace physics-manager) gravity)
|
rlm@23
|
153
|
rlm@23
|
154
|
rlm@23
|
155 ;; go through every object and add it to the physics manager
|
rlm@23
|
156 ;; if relavant.
|
rlm@23
|
157 (traverse (fn [geom]
|
rlm@23
|
158 (dorun
|
rlm@23
|
159 (for [n (range (.getNumControls geom))]
|
rlm@23
|
160 (do
|
rlm@23
|
161 (println-repl "adding control " (.getControl geom n))
|
rlm@23
|
162 (.add (.getPhysicsSpace physics-manager)
|
rlm@23
|
163 (.getControl geom n))))))
|
rlm@23
|
164 (.getRootNode this))
|
rlm@23
|
165 ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this))
|
rlm@23
|
166
|
rlm@23
|
167 (setup-fn this)
|
rlm@23
|
168 (.setDirection shadow-renderer
|
rlm@23
|
169 (.normalizeLocal (Vector3f. -1 -1 -1)))
|
rlm@23
|
170 (.addProcessor (.getViewPort this) shadow-renderer)
|
rlm@23
|
171 (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off)
|
rlm@23
|
172 ))
|
rlm@23
|
173 (simpleUpdate
|
rlm@23
|
174 [tpf]
|
rlm@23
|
175 (no-exceptions
|
rlm@23
|
176 (update-fn this tpf)))
|
rlm@23
|
177 (onAction
|
rlm@23
|
178 [binding value tpf]
|
rlm@23
|
179 ;; whenever a key is pressed, call the function returned from
|
rlm@23
|
180 ;; key-map.
|
rlm@23
|
181 (no-exceptions
|
rlm@23
|
182 (if-let [react (key-map binding)]
|
rlm@23
|
183 (react this value)))))
|
rlm@23
|
184 ;; don't show a menu to change options.
|
rlm@23
|
185
|
rlm@23
|
186 (.setShowSettings false)
|
rlm@23
|
187 (.setPauseOnLostFocus false)
|
rlm@23
|
188 (.setSettings *app-settings*))))
|
rlm@23
|
189
|
rlm@23
|
190 (defn apply-map
|
rlm@23
|
191 "Like apply, but works for maps and functions that expect an implicit map
|
rlm@23
|
192 and nothing else as in (fn [& {}]).
|
rlm@23
|
193 ------- Example -------
|
rlm@23
|
194 (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www))
|
rlm@23
|
195 (apply-map jjj {:www \"whatever\"})
|
rlm@23
|
196 -->\"whatever\""
|
rlm@23
|
197 [fn m]
|
rlm@23
|
198 (apply fn (reduce #(into %1 %2) [] m)))
|
rlm@23
|
199
|
rlm@23
|
200 #+end_src
|
rlm@23
|
201
|
rlm@23
|
202
|
rlm@23
|
203 =world= is the most important function here.
|
rlm@23
|
204 *** TODO more documentation
|
rlm@23
|
205
|
rlm@23
|
206 #+srcname: world-shapes
|
rlm@23
|
207 #+begin_src clojure :results silent
|
rlm@23
|
208 (in-ns 'cortex.world)
|
rlm@23
|
209 (defrecord shape-description
|
rlm@23
|
210 [name
|
rlm@23
|
211 color
|
rlm@23
|
212 mass
|
rlm@23
|
213 friction
|
rlm@23
|
214 texture
|
rlm@23
|
215 material
|
rlm@23
|
216 position
|
rlm@23
|
217 rotation
|
rlm@23
|
218 shape
|
rlm@23
|
219 physical?])
|
rlm@23
|
220
|
rlm@23
|
221 (def base-shape
|
rlm@23
|
222 (shape-description.
|
rlm@23
|
223 "default-shape"
|
rlm@23
|
224 false
|
rlm@23
|
225 ;;ColorRGBA/Blue
|
rlm@23
|
226 1.0 ;; mass
|
rlm@23
|
227 1.0 ;; friction
|
rlm@23
|
228 ;; texture
|
rlm@23
|
229 "Textures/Terrain/BrickWall/BrickWall.jpg"
|
rlm@23
|
230 ;; material
|
rlm@23
|
231 "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@23
|
232 Vector3f/ZERO
|
rlm@23
|
233 Quaternion/IDENTITY
|
rlm@23
|
234 (Box. Vector3f/ZERO 0.5 0.5 0.5)
|
rlm@23
|
235 true))
|
rlm@23
|
236
|
rlm@23
|
237 (defn make-shape
|
rlm@23
|
238 [#^shape-description d]
|
rlm@23
|
239 (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager))
|
rlm@23
|
240 mat (Material. asset-manager (:material d))
|
rlm@23
|
241 geom (Geometry. (:name d) (:shape d))]
|
rlm@23
|
242 (if (:texture d)
|
rlm@23
|
243 (let [key (TextureKey. (:texture d))]
|
rlm@23
|
244 (.setGenerateMips key true)
|
rlm@23
|
245 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))))
|
rlm@23
|
246 (if (:color d) (.setColor mat "Color" (:color d)))
|
rlm@23
|
247 (.setMaterial geom mat)
|
rlm@23
|
248 (if-let [rotation (:rotation d)] (.rotate geom rotation))
|
rlm@23
|
249 (.setLocalTranslation geom (:position d))
|
rlm@23
|
250 (if (:physical? d)
|
rlm@23
|
251 (let [impact-shape (doto (GImpactCollisionShape.
|
rlm@23
|
252 (.getMesh geom)) (.setMargin 0))
|
rlm@23
|
253 physics-control (RigidBodyControl.
|
rlm@23
|
254 ;;impact-shape ;; comment to disable
|
rlm@23
|
255 (float (:mass d)))]
|
rlm@23
|
256 (.createJmeMesh impact-shape)
|
rlm@23
|
257 (.addControl geom physics-control)
|
rlm@23
|
258 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
|
rlm@23
|
259 (.setFriction physics-control (:friction d))))
|
rlm@23
|
260 ;;the default is to keep this node in the physics engine forever.
|
rlm@23
|
261 ;;these commands must come after the control is added to the geometry.
|
rlm@23
|
262 ;;
|
rlm@23
|
263 geom))
|
rlm@23
|
264
|
rlm@23
|
265 (defn box
|
rlm@23
|
266 ([l w h & {:as options}]
|
rlm@23
|
267 (let [options (merge base-shape options)]
|
rlm@23
|
268 (make-shape (assoc options
|
rlm@23
|
269 :shape (Box. l w h)))))
|
rlm@23
|
270 ([] (box 0.5 0.5 0.5)))
|
rlm@23
|
271
|
rlm@23
|
272 (defn sphere
|
rlm@23
|
273 ([r & {:as options}]
|
rlm@23
|
274 (let [options (merge base-shape options)]
|
rlm@23
|
275 (make-shape (assoc options
|
rlm@23
|
276 :shape (Sphere. 32 32 (float r))))))
|
rlm@23
|
277 ([] (sphere 0.5)))
|
rlm@23
|
278
|
rlm@23
|
279 (defn add-element
|
rlm@23
|
280 ([game element node]
|
rlm@23
|
281 (.addAll
|
rlm@23
|
282 (.getPhysicsSpace
|
rlm@23
|
283 (.getState
|
rlm@23
|
284 (.getStateManager game)
|
rlm@23
|
285 BulletAppState))
|
rlm@23
|
286 element)
|
rlm@23
|
287 (.attachChild node element))
|
rlm@23
|
288 ([game element]
|
rlm@23
|
289 (add-element game element (.getRootNode game))))
|
rlm@23
|
290
|
rlm@23
|
291
|
rlm@23
|
292 (defn set-gravity*
|
rlm@23
|
293 [game gravity]
|
rlm@23
|
294 (traverse
|
rlm@23
|
295 (fn [geom]
|
rlm@23
|
296 (if-let
|
rlm@23
|
297 [control (.getControl geom RigidBodyControl)]
|
rlm@23
|
298 (do
|
rlm@23
|
299 (.setGravity control gravity)
|
rlm@23
|
300 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO)
|
rlm@23
|
301 )))
|
rlm@23
|
302 (.getRootNode game)))
|
rlm@23
|
303
|
rlm@23
|
304 #+end_src
|
rlm@23
|
305
|
rlm@23
|
306 These are convienence functions for creating JME objects and
|
rlm@23
|
307 manipulating a world.
|
rlm@23
|
308
|