rlm@29
|
1 #+title: Clojure Utilities for jMonkeyEngine3
|
rlm@23
|
2 #+author: Robert McIntyre
|
rlm@23
|
3 #+email: rlm@mit.edu
|
rlm@29
|
4 #+description:
|
rlm@29
|
5 #+keywords: JME3, clojure, import, utilities
|
rlm@23
|
6 #+SETUPFILE: ../../aurellem/org/setup.org
|
rlm@23
|
7 #+INCLUDE: ../../aurellem/org/level-0.org
|
rlm@29
|
8
|
rlm@29
|
9 * Utilities
|
rlm@29
|
10
|
rlm@29
|
11 These are a collection of functions to make programming jMonkeyEngine
|
rlm@29
|
12 in clojure easier.
|
rlm@23
|
13
|
rlm@23
|
14 ** Imports
|
rlm@28
|
15
|
rlm@23
|
16 #+srcname: import
|
rlm@23
|
17 #+begin_src clojure :results silent
|
rlm@23
|
18 (ns cortex.import
|
rlm@23
|
19 (:require swank.util.class-browse))
|
rlm@23
|
20
|
rlm@23
|
21 (defn permissive-import
|
rlm@23
|
22 [classname]
|
rlm@23
|
23 (eval `(try (import '~classname)
|
rlm@23
|
24 (catch java.lang.Exception e#
|
rlm@23
|
25 (println "couldn't import " '~classname))))
|
rlm@23
|
26 classname)
|
rlm@23
|
27
|
rlm@23
|
28 (defn jme-class? [classname]
|
rlm@23
|
29 (and
|
rlm@23
|
30 (.startsWith classname "com.jme3.")
|
rlm@23
|
31 ;; Don't import the Lwjgl stuff since it can throw exceptions
|
rlm@23
|
32 ;; upon being loaded.
|
rlm@23
|
33 (not (re-matches #".*Lwjgl.*" classname))))
|
rlm@23
|
34
|
rlm@23
|
35 (defn jme-classes
|
rlm@23
|
36 "returns a list of all jme3 classes"
|
rlm@23
|
37 []
|
rlm@23
|
38 (filter
|
rlm@23
|
39 jme-class?
|
rlm@23
|
40 (map :name
|
rlm@23
|
41 swank.util.class-browse/available-classes)))
|
rlm@23
|
42
|
rlm@23
|
43 (defn mega-import-jme3
|
rlm@23
|
44 "Import ALL the jme classes. For REPL use."
|
rlm@23
|
45 []
|
rlm@23
|
46 (doall
|
rlm@23
|
47 (map (comp permissive-import symbol) (jme-classes))))
|
rlm@23
|
48 #+end_src
|
rlm@23
|
49
|
rlm@29
|
50 jMonkeyEngine3 has a plethora of classes which can be overwhelming to
|
rlm@29
|
51 manage. This code uses reflection to import all of them. Once I'm
|
rlm@29
|
52 happy with the general structure of a namespace I can deal with
|
rlm@29
|
53 importing only the classes it actually needs.
|
rlm@29
|
54
|
rlm@23
|
55 The =mega-import-jme3= is quite usefull for debugging purposes since
|
rlm@23
|
56 it allows completion for almost all of JME's classes.
|
rlm@23
|
57
|
rlm@23
|
58 Out of curiousity, let's see just how many classes =mega-import-jme3=
|
rlm@23
|
59 imports:
|
rlm@23
|
60
|
rlm@29
|
61 #+begin_src clojure :exports both :results output
|
rlm@29
|
62 (println (clojure.core/count (cortex.import/jme-classes)) "classes")
|
rlm@23
|
63 #+end_src
|
rlm@23
|
64
|
rlm@23
|
65 #+results:
|
rlm@29
|
66 : 955 classes
|
rlm@23
|
67
|
rlm@25
|
68
|
rlm@29
|
69 ** Utilities
|
rlm@23
|
70
|
rlm@29
|
71 The utilities here come in three main groups:
|
rlm@29
|
72 - Changing settings in a running =Application=
|
rlm@29
|
73 - Creating objects
|
rlm@29
|
74 - Visualizing objects
|
rlm@23
|
75
|
rlm@23
|
76
|
rlm@29
|
77 *** Changing Settings
|
rlm@24
|
78
|
rlm@25
|
79 #+srcname: util
|
rlm@25
|
80 #+begin_src clojure
|
rlm@29
|
81 (ns cortex.util
|
rlm@29
|
82 "Utility functions for making jMonkeyEngine easier to program from
|
rlm@29
|
83 clojure"
|
rlm@29
|
84 {:author "Robert McIntyre"}
|
rlm@29
|
85 (:use cortex.world)
|
rlm@29
|
86 (:use clojure.contrib.def)
|
rlm@29
|
87 (:import com.jme3.math.Vector3f)
|
rlm@29
|
88 (:import com.jme3.math.Quaternion)
|
rlm@29
|
89 (:import com.jme3.asset.TextureKey)
|
rlm@29
|
90 (:import com.jme3.bullet.control.RigidBodyControl)
|
rlm@29
|
91 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
|
rlm@29
|
92 (:import com.jme3.scene.shape.Box)
|
rlm@29
|
93 (:import com.jme3.scene.Node)
|
rlm@29
|
94 (:import com.jme3.scene.shape.Sphere)
|
rlm@29
|
95 (:import com.jme3.light.DirectionalLight)
|
rlm@29
|
96 (:import com.jme3.math.ColorRGBA)
|
rlm@29
|
97 (:import com.jme3.bullet.BulletAppState)
|
rlm@29
|
98 (:import com.jme3.material.Material)
|
rlm@29
|
99 (:import com.jme3.scene.Geometry))
|
rlm@25
|
100
|
rlm@29
|
101 (defvar println-repl
|
rlm@29
|
102 (bound-fn [& args] (apply println args))
|
rlm@29
|
103 "println called from the LWJGL thread will not go to the REPL, but
|
rlm@29
|
104 instead to whatever terminal started the JVM process. This function
|
rlm@29
|
105 will always output to the REPL")
|
rlm@25
|
106
|
rlm@29
|
107 (defn position-camera
|
rlm@29
|
108 ([game position direction up]
|
rlm@29
|
109 (doto (.getCamera game)
|
rlm@29
|
110 (.setLocation )
|
rlm@29
|
111 (.lookAt direction up)))
|
rlm@29
|
112 ([game position direction]
|
rlm@29
|
113 (position-camera
|
rlm@29
|
114 game position direction Vector3f/UNIT_Y)))
|
rlm@25
|
115
|
rlm@29
|
116 (defn enable-debug
|
rlm@29
|
117 "Turn on the debug wireframes for every object in this simulation"
|
rlm@29
|
118 [world]
|
rlm@29
|
119 (.enableDebug
|
rlm@29
|
120 (.getPhysicsSpace
|
rlm@29
|
121 (.getState
|
rlm@29
|
122 (.getStateManager world)
|
rlm@29
|
123 BulletAppState))
|
rlm@29
|
124 (asset-manager)))
|
rlm@29
|
125
|
rlm@29
|
126 (defn set-gravity
|
rlm@29
|
127 "In order to change the gravity of a scene, it is not only necessary
|
rlm@29
|
128 to set the gravity variable, but to \"tap\" every physics object in
|
rlm@29
|
129 the scene to reactivate physics calculations."
|
rlm@25
|
130 [game gravity]
|
rlm@25
|
131 (traverse
|
rlm@25
|
132 (fn [geom]
|
rlm@25
|
133 (if-let
|
rlm@29
|
134 ;; only set gravity for physical objects.
|
rlm@25
|
135 [control (.getControl geom RigidBodyControl)]
|
rlm@25
|
136 (do
|
rlm@25
|
137 (.setGravity control gravity)
|
rlm@29
|
138 ;; tappsies!
|
rlm@29
|
139 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
|
rlm@25
|
140 (.getRootNode game)))
|
rlm@29
|
141
|
rlm@29
|
142 (defn add-element
|
rlm@29
|
143 "Add the Spatial to the game's environment"
|
rlm@29
|
144 ([game element node]
|
rlm@29
|
145 (.addAll
|
rlm@29
|
146 (.getPhysicsSpace
|
rlm@29
|
147 (.getState
|
rlm@29
|
148 (.getStateManager game)
|
rlm@29
|
149 BulletAppState))
|
rlm@29
|
150 element)
|
rlm@29
|
151 (.attachChild node element))
|
rlm@29
|
152 ([game element]
|
rlm@29
|
153 (add-element game element (.getRootNode game))))
|
rlm@29
|
154
|
rlm@29
|
155 (defn apply-map
|
rlm@29
|
156 "Like apply, but works for maps and functions that expect an
|
rlm@29
|
157 implicit map and nothing else as in (fn [& {}]).
|
rlm@29
|
158 ------- Example -------
|
rlm@29
|
159 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
|
rlm@29
|
160 (println www))
|
rlm@29
|
161 (apply-map demo {:www \"hello!\"})
|
rlm@29
|
162 -->\"hello\""
|
rlm@29
|
163 [fn m]
|
rlm@29
|
164 (apply fn (reduce #(into %1 %2) [] m)))
|
rlm@29
|
165
|
rlm@25
|
166 #+end_src
|
rlm@25
|
167
|
rlm@25
|
168
|
rlm@29
|
169 *** Creating Basic Shapes
|
rlm@29
|
170
|
rlm@25
|
171 #+srcname: shapes
|
rlm@25
|
172 #+begin_src clojure :results silent
|
rlm@25
|
173 (in-ns 'cortex.util)
|
rlm@29
|
174
|
rlm@25
|
175 (defrecord shape-description
|
rlm@25
|
176 [name
|
rlm@25
|
177 color
|
rlm@25
|
178 mass
|
rlm@25
|
179 friction
|
rlm@25
|
180 texture
|
rlm@25
|
181 material
|
rlm@25
|
182 position
|
rlm@25
|
183 rotation
|
rlm@25
|
184 shape
|
rlm@29
|
185 physical?
|
rlm@29
|
186 GImpact?
|
rlm@29
|
187 ])
|
rlm@25
|
188
|
rlm@25
|
189 (def base-shape
|
rlm@25
|
190 (shape-description.
|
rlm@25
|
191 "default-shape"
|
rlm@25
|
192 false
|
rlm@25
|
193 ;;ColorRGBA/Blue
|
rlm@25
|
194 1.0 ;; mass
|
rlm@25
|
195 1.0 ;; friction
|
rlm@25
|
196 ;; texture
|
rlm@25
|
197 "Textures/Terrain/BrickWall/BrickWall.jpg"
|
rlm@25
|
198 ;; material
|
rlm@25
|
199 "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@25
|
200 Vector3f/ZERO
|
rlm@25
|
201 Quaternion/IDENTITY
|
rlm@25
|
202 (Box. Vector3f/ZERO 0.5 0.5 0.5)
|
rlm@29
|
203 true
|
rlm@29
|
204 false))
|
rlm@25
|
205
|
rlm@25
|
206 (defn make-shape
|
rlm@25
|
207 [#^shape-description d]
|
rlm@29
|
208 (let [asset-manager (asset-manager)
|
rlm@25
|
209 mat (Material. asset-manager (:material d))
|
rlm@25
|
210 geom (Geometry. (:name d) (:shape d))]
|
rlm@25
|
211 (if (:texture d)
|
rlm@25
|
212 (let [key (TextureKey. (:texture d))]
|
rlm@25
|
213 (.setGenerateMips key true)
|
rlm@25
|
214 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))))
|
rlm@25
|
215 (if (:color d) (.setColor mat "Color" (:color d)))
|
rlm@25
|
216 (.setMaterial geom mat)
|
rlm@25
|
217 (if-let [rotation (:rotation d)] (.rotate geom rotation))
|
rlm@25
|
218 (.setLocalTranslation geom (:position d))
|
rlm@25
|
219 (if (:physical? d)
|
rlm@29
|
220 (let [physics-control
|
rlm@29
|
221 (if (:GImpact d)
|
rlm@29
|
222 ;; Create an accurate mesh collision shape if desired.
|
rlm@29
|
223 (RigidBodyControl.
|
rlm@29
|
224 (doto (GImpactCollisionShape.
|
rlm@29
|
225 (.getMesh geom))
|
rlm@29
|
226 (.createJmeMesh)
|
rlm@29
|
227 (.setMargin 0))
|
rlm@29
|
228 (float (:mass d)))
|
rlm@29
|
229 ;; otherwise use jme3's default
|
rlm@29
|
230 (RigidBodyControl. (float (:mass d))))]
|
rlm@25
|
231 (.addControl geom physics-control)
|
rlm@25
|
232 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
|
rlm@25
|
233 (.setFriction physics-control (:friction d))))
|
rlm@25
|
234 geom))
|
rlm@25
|
235
|
rlm@25
|
236 (defn box
|
rlm@25
|
237 ([l w h & {:as options}]
|
rlm@25
|
238 (let [options (merge base-shape options)]
|
rlm@25
|
239 (make-shape (assoc options
|
rlm@25
|
240 :shape (Box. l w h)))))
|
rlm@25
|
241 ([] (box 0.5 0.5 0.5)))
|
rlm@25
|
242
|
rlm@25
|
243 (defn sphere
|
rlm@25
|
244 ([r & {:as options}]
|
rlm@25
|
245 (let [options (merge base-shape options)]
|
rlm@25
|
246 (make-shape (assoc options
|
rlm@25
|
247 :shape (Sphere. 32 32 (float r))))))
|
rlm@25
|
248 ([] (sphere 0.5)))
|
rlm@29
|
249 #+end_src
|
rlm@25
|
250
|
rlm@25
|
251
|
rlm@29
|
252 *** Viewing Objects
|
rlm@25
|
253
|
rlm@29
|
254 #+srcname: world-view
|
rlm@29
|
255 #+begin_src clojure :results silent
|
rlm@29
|
256 (in-ns 'cortex.util)
|
rlm@25
|
257
|
rlm@29
|
258 (defprotocol Viewable
|
rlm@29
|
259 (view [something]))
|
rlm@29
|
260
|
rlm@29
|
261 (extend-type com.jme3.scene.Geometry
|
rlm@29
|
262 Viewable
|
rlm@29
|
263 (view [geo]
|
rlm@29
|
264 (view (doto (Node.)(.attachChild geo)))))
|
rlm@29
|
265
|
rlm@29
|
266 (extend-type com.jme3.scene.Node
|
rlm@29
|
267 Viewable
|
rlm@29
|
268 (view
|
rlm@29
|
269 [node]
|
rlm@29
|
270 (.start
|
rlm@29
|
271 (world
|
rlm@29
|
272 node
|
rlm@29
|
273 {}
|
rlm@29
|
274 (fn [world]
|
rlm@29
|
275 (enable-debug world)
|
rlm@29
|
276 (set-gravity world Vector3f/ZERO)
|
rlm@29
|
277 (let [sun
|
rlm@29
|
278 (doto (DirectionalLight.)
|
rlm@29
|
279 (.setDirection
|
rlm@29
|
280 (.normalizeLocal (Vector3f. 1 0 -2)))
|
rlm@29
|
281 (.setColor ColorRGBA/White))]
|
rlm@29
|
282 (.addLight (.getRootNode world) sun)))
|
rlm@29
|
283 no-op))))
|
rlm@25
|
284 #+end_src
|
rlm@25
|
285
|
rlm@29
|
286 Here I make the =Viewable= protocol and extend it to JME's types. Now
|
rlm@29
|
287 hello-world can be written as easily as:
|
rlm@29
|
288
|
rlm@29
|
289 #+begin_src clojure :results silent
|
rlm@29
|
290 (cortex.util/view (cortex.util/box))
|
rlm@29
|
291 #+end_src
|
rlm@29
|
292
|
rlm@29
|
293
|
rlm@24
|
294 * COMMENT code generation
|
rlm@24
|
295 #+begin_src clojure :tangle ../src/cortex/import.clj
|
rlm@24
|
296 <<import>>
|
rlm@24
|
297 #+end_src
|
rlm@25
|
298
|
rlm@25
|
299
|
rlm@29
|
300 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
|
rlm@25
|
301 <<util>>
|
rlm@25
|
302 <<shapes>>
|
rlm@29
|
303 <<world-view>>
|
rlm@25
|
304 #+end_src
|
rlm@25
|
305
|
rlm@29
|
306
|
rlm@29
|
307
|
rlm@29
|
308
|
rlm@29
|
309
|
rlm@29
|
310
|
rlm@29
|
311
|
rlm@29
|
312
|
rlm@29
|
313
|
rlm@29
|
314
|
rlm@29
|
315
|
rlm@29
|
316
|
rlm@29
|
317
|
rlm@29
|
318
|
rlm@29
|
319
|
rlm@29
|
320
|
rlm@29
|
321
|