Mercurial > cortex
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 |