comparison org/cortex.org @ 23:cab2da252494

split off the rest of cortex.org
author Robert McIntyre <rlm@mit.edu>
date Sun, 23 Oct 2011 23:54:26 -0700
parents 01e1427126af
children 775d97247dd0
comparison
equal deleted inserted replaced
22:157b416152ea 23:cab2da252494
7 #+babel: :mkdirp yes :noweb yes :exports both 7 #+babel: :mkdirp yes :noweb yes :exports both
8 8
9 9
10 * Simulation Base 10 * Simulation Base
11 11
12 ** Imports
13 jMonkeyEngine has a plethora of classes which can be overwhelming at
14 first. So that I one can get right to coding, it's good to take the
15 time right now and make a "import all" function which brings in all of
16 the important jme3 classes. Once I'm happy with the general structure
17 of a namespace I can deal with importing only the classes it actually
18 needs.
19
20 #+srcname: import
21 #+begin_src clojure :results silent
22 (ns cortex.import
23 (:require swank.util.class-browse))
24
25 (defn permissive-import
26 [classname]
27 (eval `(try (import '~classname)
28 (catch java.lang.Exception e#
29 (println "couldn't import " '~classname))))
30 classname)
31
32 (defn jme-class? [classname]
33 (and
34 (.startsWith classname "com.jme3.")
35 ;; Don't import the Lwjgl stuff since it can throw exceptions
36 ;; upon being loaded.
37 (not (re-matches #".*Lwjgl.*" classname))))
38
39 (defn jme-classes
40 "returns a list of all jme3 classes"
41 []
42 (filter
43 jme-class?
44 (map :name
45 swank.util.class-browse/available-classes)))
46
47 (defn mega-import-jme3
48 "Import ALL the jme classes. For REPL use."
49 []
50 (doall
51 (map (comp permissive-import symbol) (jme-classes))))
52 #+end_src
53
54 The =mega-import-jme3= is quite usefull for debugging purposes since
55 it allows completion for almost all of JME's classes.
56
57 Out of curiousity, let's see just how many classes =mega-import-jme3=
58 imports:
59
60 #+begin_src clojure :exports both
61 (clojure.core/count (cortex.import/jme-classes))
62 #+end_src
63
64 #+results:
65 : 955
66
67 ** Simplification
68 *** World
69
70 It is comvienent to wrap the JME elements that deal with creating a
71 world, creation of basic objects, and Keyboard input with a nicer
72 interface (at least for my purposes).
73
74 #+srcname: world-inputs
75 #+begin_src clojure :results silent
76 (ns cortex.world)
77 (require 'cortex.import)
78 (use 'clojure.contrib.def)
79 (rlm.rlm-commands/help)
80 (cortex.import/mega-import-jme3)
81
82 (defvar *app-settings*
83 (doto (AppSettings. true)
84 (.setFullscreen false)
85 (.setTitle "Aurellem.")
86 ;; disable 32 bit stuff for now
87 ;;(.setAudioRenderer "Send")
88 )
89 "These settings control how the game is displayed on the screen for
90 debugging purposes. Use binding forms to change this if desired.
91 Full-screen mode does not work on some computers.")
92
93 (defn asset-manager
94 "returns a new, configured assetManager" []
95 (JmeSystem/newAssetManager
96 (.getResource
97 (.getContextClassLoader (Thread/currentThread))
98 "com/jme3/asset/Desktop.cfg")))
99
100 (defmacro no-exceptions
101 "Sweet relief like I never knew."
102 [& forms]
103 `(try ~@forms (catch Exception e# (.printStackTrace e#))))
104
105 (defn thread-exception-removal []
106 (println "removing exceptions from " (Thread/currentThread))
107 (.setUncaughtExceptionHandler
108 (Thread/currentThread)
109 (proxy [Thread$UncaughtExceptionHandler] []
110 (uncaughtException
111 [thread thrown]
112 (println "uncaught-exception thrown in " thread)
113 (println (.getMessage thrown))))))
114
115 (def println-repl (bound-fn [& args] (apply println args)))
116
117 (use '[pokemon [lpsolve :only [constant-map]]])
118
119 (defn no-op [& _])
120
121 (defn all-keys
122 "Construct a map of strings representing all the manual inputs from
123 either the keyboard or mouse."
124 []
125 (let [inputs (constant-map KeyInput)]
126 (assoc
127 (zipmap (map (fn [field]
128 (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs))
129 (map (fn [val] (KeyTrigger. val)) (keys inputs)))
130 ;;explicitly add mouse controls
131 "mouse-left" (MouseButtonTrigger. 0)
132 "mouse-middle" (MouseButtonTrigger. 2)
133 "mouse-right" (MouseButtonTrigger. 1))))
134
135 (defn initialize-inputs
136 "more java-interop cruft to establish keybindings for a particular virtual world"
137 [game input-manager key-map]
138 (doall (map (fn [[name trigger]]
139 (.addMapping ^InputManager input-manager
140 name (into-array (class trigger) [trigger]))) key-map))
141 (doall (map (fn [name]
142 (.addListener ^InputManager input-manager game
143 (into-array String [name]))) (keys key-map))))
144
145 #+end_src
146
147 These functions are all for debug controlling of the world through
148 keyboard and mouse.
149
150 We reuse =constant-map= from =pokemon.lpsolve= to get the numerical
151 values for all the keys defined in the =KeyInput= class. The
152 documentation for =constant-map= is:
153
154 #+begin_src clojure :results output
155 (doc pokemon.lpsolve/constant-map)
156 #+end_src
157
158 #+results:
159 : -------------------------
160 : pokemon.lpsolve/constant-map
161 : ([class])
162 : Takes a class and creates a map of the static constant integer
163 : fields with their names. This helps with C wrappers where they have
164 : just defined a bunch of integer constants instead of enums
165
166
167 Then, =all-keys= converts the constant names like =KEY_J= to the more
168 clojure-like =key-j=, and returns a map from these keys to
169 jMonkeyEngine KeyTrigger objects, the use of which will soon become
170 apparent. =all-keys= also adds the three mouse button controls to the
171 map.
172
173 #+srcname: world
174 #+begin_src clojure :results silent
175 (in-ns 'cortex.world)
176
177 (defn traverse
178 "apply f to every non-node, deeply"
179 [f node]
180 (if (isa? (class node) Node)
181 (dorun (map (partial traverse f) (.getChildren node)))
182 (f node)))
183
184 (def gravity (Vector3f. 0 -9.81 0))
185
186 (defn world
187 [root-node key-map setup-fn update-fn]
188 (let [physics-manager (BulletAppState.)
189 shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256))
190 ;;maybe use a better shadow renderer someday!
191 ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1)
192 ]
193 (doto
194 (proxy [SimpleApplication ActionListener] []
195 (simpleInitApp
196 []
197 (no-exceptions
198 (.setTimer this (IsoTimer. 60))
199 ;; Create key-map.
200 (.setFrustumFar (.getCamera this) 300)
201 (initialize-inputs this (.getInputManager this) (all-keys))
202 ;; Don't take control of the mouse
203 (org.lwjgl.input.Mouse/setGrabbed false)
204 ;; add all objects to the world
205 (.attachChild (.getRootNode this) root-node)
206 ;; enable physics
207 ;; add a physics manager
208 (.attach (.getStateManager this) physics-manager)
209 (.setGravity (.getPhysicsSpace physics-manager) gravity)
210
211
212 ;; go through every object and add it to the physics manager
213 ;; if relavant.
214 (traverse (fn [geom]
215 (dorun
216 (for [n (range (.getNumControls geom))]
217 (do
218 (println-repl "adding control " (.getControl geom n))
219 (.add (.getPhysicsSpace physics-manager)
220 (.getControl geom n))))))
221 (.getRootNode this))
222 ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this))
223
224 (setup-fn this)
225 (.setDirection shadow-renderer
226 (.normalizeLocal (Vector3f. -1 -1 -1)))
227 (.addProcessor (.getViewPort this) shadow-renderer)
228 (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off)
229 ))
230 (simpleUpdate
231 [tpf]
232 (no-exceptions
233 (update-fn this tpf)))
234 (onAction
235 [binding value tpf]
236 ;; whenever a key is pressed, call the function returned from
237 ;; key-map.
238 (no-exceptions
239 (if-let [react (key-map binding)]
240 (react this value)))))
241 ;; don't show a menu to change options.
242
243 (.setShowSettings false)
244 (.setPauseOnLostFocus false)
245 (.setSettings *app-settings*))))
246
247 (defn apply-map
248 "Like apply, but works for maps and functions that expect an implicit map
249 and nothing else as in (fn [& {}]).
250 ------- Example -------
251 (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www))
252 (apply-map jjj {:www \"whatever\"})
253 -->\"whatever\""
254 [fn m]
255 (apply fn (reduce #(into %1 %2) [] m)))
256
257 #+end_src
258
259
260 =world= is the most important function here.
261 *** TODO more documentation
262
263 #+srcname: world-shapes
264 #+begin_src clojure :results silent
265 (in-ns 'cortex.world)
266 (defrecord shape-description
267 [name
268 color
269 mass
270 friction
271 texture
272 material
273 position
274 rotation
275 shape
276 physical?])
277
278 (def base-shape
279 (shape-description.
280 "default-shape"
281 false
282 ;;ColorRGBA/Blue
283 1.0 ;; mass
284 1.0 ;; friction
285 ;; texture
286 "Textures/Terrain/BrickWall/BrickWall.jpg"
287 ;; material
288 "Common/MatDefs/Misc/Unshaded.j3md"
289 Vector3f/ZERO
290 Quaternion/IDENTITY
291 (Box. Vector3f/ZERO 0.5 0.5 0.5)
292 true))
293
294 (defn make-shape
295 [#^shape-description d]
296 (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager))
297 mat (Material. asset-manager (:material d))
298 geom (Geometry. (:name d) (:shape d))]
299 (if (:texture d)
300 (let [key (TextureKey. (:texture d))]
301 (.setGenerateMips key true)
302 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))))
303 (if (:color d) (.setColor mat "Color" (:color d)))
304 (.setMaterial geom mat)
305 (if-let [rotation (:rotation d)] (.rotate geom rotation))
306 (.setLocalTranslation geom (:position d))
307 (if (:physical? d)
308 (let [impact-shape (doto (GImpactCollisionShape.
309 (.getMesh geom)) (.setMargin 0))
310 physics-control (RigidBodyControl.
311 ;;impact-shape ;; comment to disable
312 (float (:mass d)))]
313 (.createJmeMesh impact-shape)
314 (.addControl geom physics-control)
315 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
316 (.setFriction physics-control (:friction d))))
317 ;;the default is to keep this node in the physics engine forever.
318 ;;these commands must come after the control is added to the geometry.
319 ;;
320 geom))
321
322 (defn box
323 ([l w h & {:as options}]
324 (let [options (merge base-shape options)]
325 (make-shape (assoc options
326 :shape (Box. l w h)))))
327 ([] (box 0.5 0.5 0.5)))
328
329 (defn sphere
330 ([r & {:as options}]
331 (let [options (merge base-shape options)]
332 (make-shape (assoc options
333 :shape (Sphere. 32 32 (float r))))))
334 ([] (sphere 0.5)))
335
336 (defn add-element
337 ([game element node]
338 (.addAll
339 (.getPhysicsSpace
340 (.getState
341 (.getStateManager game)
342 BulletAppState))
343 element)
344 (.attachChild node element))
345 ([game element]
346 (add-element game element (.getRootNode game))))
347
348
349 (defn set-gravity*
350 [game gravity]
351 (traverse
352 (fn [geom]
353 (if-let
354 [control (.getControl geom RigidBodyControl)]
355 (do
356 (.setGravity control gravity)
357 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO)
358 )))
359 (.getRootNode game)))
360
361 #+end_src
362
363 These are convienence functions for creating JME objects and
364 manipulating a world.
365
366 #+srcname: world-view
367 #+begin_src clojure :results silent
368 (in-ns 'cortex.world)
369
370 (defprotocol Viewable
371 (view [something]))
372
373 (extend-type com.jme3.scene.Geometry
374 Viewable
375 (view [geo]
376 (view (doto (Node.)(.attachChild geo)))))
377
378 (extend-type com.jme3.scene.Node
379 Viewable
380 (view [node]
381 (.start
382 (world node
383 {}
384 (fn [world]
385 (.enableDebug
386 (.getPhysicsSpace
387 (.getState
388 (.getStateManager world)
389 BulletAppState))
390 (asset-manager))
391 (set-gravity* world Vector3f/ZERO)
392 ;; (set-gravity* world (Vector3f. 0 (float -0.4) 0))
393 (let [sun (doto (DirectionalLight.)
394 (.setDirection (.normalizeLocal (Vector3f. 1 0 -2)))
395 (.setColor ColorRGBA/White))]
396 (.addLight (.getRootNode world) sun)))
397 no-op))))
398
399 (defn position-camera [game]
400 (doto (.getCamera game)
401 (.setLocation (Vector3f. 0 6 6))
402 (.lookAt Vector3f/ZERO (Vector3f. 0 1 0))))
403
404 #+end_src
405
406 Here I make the =Viewable= protocol and extend it to JME's types. Now
407 hello-world can be written as easily as:
408
409 #+begin_src clojure :results silent
410 (cortex.world/view (cortex.world/box))
411 #+end_src
412 12
413 ** Hello 13 ** Hello
414 Here are the jmonkeyengine "Hello" programs translated to clojure. 14 Here are the jmonkeyengine "Hello" programs translated to clojure.
415 *** Hello Simple App 15 *** Hello Simple App
416 Here is the hello world example for jme3 in clojure. 16 Here is the hello world example for jme3 in clojure. It's a more or
417 It's a more or less direct translation from the java source 17 less direct translation from the java source [[http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_simpleapplication][here]].
418 from
419 http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_simpleapplication.
420 18
421 Of note is the fact that since we don't have access to the 19 Of note is the fact that since we don't have access to the
422 =AssetManager= via extendig =SimpleApplication=, we have to build one 20 =AssetManager= via extendig =SimpleApplication=, we have to build one
423 ourselves. 21 ourselves.
424 22