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@34
|
9 [TABLE-OF-CONTENTS]
|
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@34
|
14 * Imports
|
rlm@28
|
15
|
rlm@66
|
16 #+name: import
|
rlm@23
|
17 #+begin_src clojure :results silent
|
rlm@23
|
18 (ns cortex.import
|
rlm@388
|
19 (:import java.io.File java.util.jar.JarFile))
|
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@306
|
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@388
|
35 (defn jme-jars []
|
rlm@388
|
36 (map
|
rlm@388
|
37 #(JarFile. (File. %))
|
rlm@388
|
38 (filter (partial re-matches #".*jME3.*")
|
rlm@388
|
39 (clojure.string/split
|
rlm@388
|
40 (System/getProperty "java.class.path") #":"))))
|
rlm@388
|
41
|
rlm@388
|
42 (defn jme-class-names []
|
rlm@23
|
43 (filter
|
rlm@23
|
44 jme-class?
|
rlm@388
|
45 (map
|
rlm@388
|
46 (comp
|
rlm@388
|
47 #(.replace % File/separator ".")
|
rlm@388
|
48 #(clojure.string/replace % ".class" ""))
|
rlm@388
|
49 (filter
|
rlm@388
|
50 (partial re-matches #".*\.class$")
|
rlm@388
|
51 (mapcat
|
rlm@388
|
52 #(map
|
rlm@388
|
53 str
|
rlm@388
|
54 (enumeration-seq
|
rlm@388
|
55 (.entries %)))
|
rlm@388
|
56 (jme-jars))))))
|
rlm@388
|
57
|
rlm@23
|
58 (defn mega-import-jme3
|
rlm@23
|
59 "Import ALL the jme classes. For REPL use."
|
rlm@23
|
60 []
|
rlm@388
|
61 (dorun
|
rlm@459
|
62 (import com.aurellem.capture.IsoTimer)
|
rlm@388
|
63 (map (comp permissive-import symbol) (jme-class-names))))
|
rlm@23
|
64 #+end_src
|
rlm@23
|
65
|
rlm@29
|
66 jMonkeyEngine3 has a plethora of classes which can be overwhelming to
|
rlm@29
|
67 manage. This code uses reflection to import all of them. Once I'm
|
rlm@29
|
68 happy with the general structure of a namespace I can deal with
|
rlm@29
|
69 importing only the classes it actually needs.
|
rlm@29
|
70
|
rlm@306
|
71 The =mega-import-jme3= is quite useful for debugging purposes since
|
rlm@34
|
72 it allows completion for almost all of JME's classes from the REPL.
|
rlm@23
|
73
|
rlm@306
|
74 Out of curiosity, let's see just how many classes =mega-import-jme3=
|
rlm@23
|
75 imports:
|
rlm@23
|
76
|
rlm@29
|
77 #+begin_src clojure :exports both :results output
|
rlm@455
|
78 (println (clojure.core/count (cortex.import/jme-class-names)) "classes")
|
rlm@23
|
79 #+end_src
|
rlm@23
|
80
|
rlm@23
|
81 #+results:
|
rlm@455
|
82 : 938 classes
|
rlm@23
|
83
|
rlm@25
|
84
|
rlm@34
|
85 * Utilities
|
rlm@23
|
86
|
rlm@29
|
87 The utilities here come in three main groups:
|
rlm@29
|
88 - Changing settings in a running =Application=
|
rlm@29
|
89 - Creating objects
|
rlm@50
|
90 - Debug Actions
|
rlm@29
|
91 - Visualizing objects
|
rlm@23
|
92
|
rlm@29
|
93 *** Changing Settings
|
rlm@24
|
94
|
rlm@66
|
95 #+name: util
|
rlm@25
|
96 #+begin_src clojure
|
rlm@29
|
97 (ns cortex.util
|
rlm@34
|
98 "Utility functions for making jMonkeyEngine3 easier to program from
|
rlm@34
|
99 clojure."
|
rlm@29
|
100 {:author "Robert McIntyre"}
|
rlm@29
|
101 (:use cortex.world)
|
rlm@29
|
102 (:import com.jme3.math.Vector3f)
|
rlm@29
|
103 (:import com.jme3.math.Quaternion)
|
rlm@29
|
104 (:import com.jme3.asset.TextureKey)
|
rlm@29
|
105 (:import com.jme3.bullet.control.RigidBodyControl)
|
rlm@29
|
106 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
|
rlm@29
|
107 (:import com.jme3.scene.shape.Box)
|
rlm@29
|
108 (:import com.jme3.scene.Node)
|
rlm@29
|
109 (:import com.jme3.scene.shape.Sphere)
|
rlm@47
|
110 (:import com.jme3.light.AmbientLight)
|
rlm@29
|
111 (:import com.jme3.light.DirectionalLight)
|
rlm@114
|
112 (:import (com.jme3.math Triangle ColorRGBA))
|
rlm@29
|
113 (:import com.jme3.bullet.BulletAppState)
|
rlm@29
|
114 (:import com.jme3.material.Material)
|
rlm@40
|
115 (:import com.jme3.scene.Geometry)
|
rlm@114
|
116 (:import java.awt.image.BufferedImage)
|
rlm@114
|
117 (:import javax.swing.JPanel)
|
rlm@114
|
118 (:import javax.swing.JFrame)
|
rlm@335
|
119 (:import ij.ImagePlus)
|
rlm@114
|
120 (:import javax.swing.SwingUtilities)
|
rlm@192
|
121 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)
|
rlm@40
|
122 (:import (java.util.logging Level Logger)))
|
rlm@40
|
123
|
rlm@320
|
124 (def println-repl
|
rlm@29
|
125 "println called from the LWJGL thread will not go to the REPL, but
|
rlm@114
|
126 instead to whatever terminal started the JVM process. This function
|
rlm@320
|
127 will always output to the REPL"
|
rlm@320
|
128 (bound-fn [& args] (apply println args)))
|
rlm@25
|
129
|
rlm@29
|
130 (defn position-camera
|
rlm@34
|
131 "Change the position of the in-world camera."
|
rlm@400
|
132 ([world #^Vector3f position #^Quaternion rotation]
|
rlm@34
|
133 (doto (.getCamera world)
|
rlm@297
|
134 (.setLocation position)
|
rlm@297
|
135 (.setRotation rotation)))
|
rlm@400
|
136 ([world [position rotation]]
|
rlm@400
|
137 (position-camera world position rotation)))
|
rlm@400
|
138
|
rlm@25
|
139
|
rlm@29
|
140 (defn enable-debug
|
rlm@34
|
141 "Turn on debug wireframes for every object in this simulation."
|
rlm@29
|
142 [world]
|
rlm@29
|
143 (.enableDebug
|
rlm@29
|
144 (.getPhysicsSpace
|
rlm@29
|
145 (.getState
|
rlm@29
|
146 (.getStateManager world)
|
rlm@29
|
147 BulletAppState))
|
rlm@29
|
148 (asset-manager)))
|
rlm@29
|
149
|
rlm@78
|
150 (defn speed-up
|
rlm@78
|
151 "Increase the dismally slow speed of the world's camera."
|
rlm@451
|
152 ([world] (speed-up world 1))
|
rlm@451
|
153 ([world amount]
|
rlm@451
|
154 (.setMoveSpeed (.getFlyByCamera world)
|
rlm@451
|
155 (float (* amount 60)))
|
rlm@451
|
156 (.setRotationSpeed (.getFlyByCamera world)
|
rlm@451
|
157 (float (* amount 3)))
|
rlm@451
|
158 world))
|
rlm@78
|
159
|
rlm@40
|
160 (defn no-logging
|
rlm@40
|
161 "Disable all of jMonkeyEngine's logging."
|
rlm@40
|
162 []
|
rlm@40
|
163 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
|
rlm@40
|
164
|
rlm@40
|
165 (defn set-accuracy
|
rlm@40
|
166 "Change the accuracy at which the World's Physics is calculated."
|
rlm@40
|
167 [world new-accuracy]
|
rlm@40
|
168 (let [physics-manager
|
rlm@40
|
169 (.getState
|
rlm@40
|
170 (.getStateManager world) BulletAppState)]
|
rlm@40
|
171 (.setAccuracy
|
rlm@40
|
172 (.getPhysicsSpace physics-manager)
|
rlm@40
|
173 (float new-accuracy))))
|
rlm@40
|
174
|
rlm@40
|
175
|
rlm@34
|
176 (defn set-gravity
|
rlm@29
|
177 "In order to change the gravity of a scene, it is not only necessary
|
rlm@29
|
178 to set the gravity variable, but to \"tap\" every physics object in
|
rlm@29
|
179 the scene to reactivate physics calculations."
|
rlm@34
|
180 [world gravity]
|
rlm@25
|
181 (traverse
|
rlm@25
|
182 (fn [geom]
|
rlm@25
|
183 (if-let
|
rlm@29
|
184 ;; only set gravity for physical objects.
|
rlm@25
|
185 [control (.getControl geom RigidBodyControl)]
|
rlm@25
|
186 (do
|
rlm@25
|
187 (.setGravity control gravity)
|
rlm@29
|
188 ;; tappsies!
|
rlm@29
|
189 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
|
rlm@34
|
190 (.getRootNode world)))
|
rlm@29
|
191
|
rlm@29
|
192 (defn add-element
|
rlm@34
|
193 "Add the Spatial to the world's environment"
|
rlm@34
|
194 ([world element node]
|
rlm@29
|
195 (.addAll
|
rlm@29
|
196 (.getPhysicsSpace
|
rlm@29
|
197 (.getState
|
rlm@34
|
198 (.getStateManager world)
|
rlm@29
|
199 BulletAppState))
|
rlm@29
|
200 element)
|
rlm@29
|
201 (.attachChild node element))
|
rlm@34
|
202 ([world element]
|
rlm@34
|
203 (add-element world element (.getRootNode world))))
|
rlm@29
|
204
|
rlm@29
|
205 (defn apply-map
|
rlm@29
|
206 "Like apply, but works for maps and functions that expect an
|
rlm@29
|
207 implicit map and nothing else as in (fn [& {}]).
|
rlm@29
|
208 ------- Example -------
|
rlm@29
|
209 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
|
rlm@29
|
210 (println www))
|
rlm@29
|
211 (apply-map demo {:www \"hello!\"})
|
rlm@29
|
212 -->\"hello\""
|
rlm@29
|
213 [fn m]
|
rlm@29
|
214 (apply fn (reduce #(into %1 %2) [] m)))
|
rlm@29
|
215
|
rlm@151
|
216 (defn map-vals
|
rlm@151
|
217 "Transform a map by applying a function to its values,
|
rlm@151
|
218 keeping the keys the same."
|
rlm@151
|
219 [f m] (zipmap (keys m) (map f (vals m))))
|
rlm@151
|
220
|
rlm@164
|
221 (defn runonce
|
rlm@164
|
222 "Decorator. returns a function which will run only once.
|
rlm@164
|
223 Inspired by Halloway's version from Lancet."
|
rlm@164
|
224 {:author "Robert McIntyre"}
|
rlm@164
|
225 [function]
|
rlm@164
|
226 (let [sentinel (Object.)
|
rlm@164
|
227 result (atom sentinel)]
|
rlm@164
|
228 (fn [& args]
|
rlm@164
|
229 (locking sentinel
|
rlm@164
|
230 (if (= @result sentinel)
|
rlm@164
|
231 (reset! result (apply function args))
|
rlm@164
|
232 @result)))))
|
rlm@164
|
233
|
rlm@151
|
234
|
rlm@25
|
235 #+end_src
|
rlm@25
|
236
|
rlm@47
|
237 #+results: util
|
rlm@297
|
238 : #'cortex.util/runonce
|
rlm@73
|
239
|
rlm@25
|
240
|
rlm@29
|
241 *** Creating Basic Shapes
|
rlm@29
|
242
|
rlm@66
|
243 #+name: shapes
|
rlm@25
|
244 #+begin_src clojure :results silent
|
rlm@25
|
245 (in-ns 'cortex.util)
|
rlm@29
|
246
|
rlm@153
|
247 (defn load-bullet
|
rlm@306
|
248 "Running this function unpacks the native bullet libraries and makes
|
rlm@153
|
249 them available."
|
rlm@153
|
250 []
|
rlm@153
|
251 (let [sim (world (Node.) {} no-op no-op)]
|
rlm@153
|
252 (doto sim
|
rlm@153
|
253 (.enqueue
|
rlm@153
|
254 (fn []
|
rlm@153
|
255 (.stop sim)))
|
rlm@153
|
256 (.start))))
|
rlm@153
|
257
|
rlm@153
|
258
|
rlm@25
|
259 (defrecord shape-description
|
rlm@25
|
260 [name
|
rlm@25
|
261 color
|
rlm@25
|
262 mass
|
rlm@25
|
263 friction
|
rlm@25
|
264 texture
|
rlm@25
|
265 material
|
rlm@25
|
266 position
|
rlm@25
|
267 rotation
|
rlm@25
|
268 shape
|
rlm@29
|
269 physical?
|
rlm@29
|
270 GImpact?
|
rlm@29
|
271 ])
|
rlm@25
|
272
|
rlm@320
|
273 (def base-shape
|
rlm@320
|
274 "Basic settings for shapes."
|
rlm@320
|
275 (shape-description.
|
rlm@320
|
276 "default-shape"
|
rlm@320
|
277 false
|
rlm@320
|
278 ;;ColorRGBA/Blue
|
rlm@320
|
279 1.0 ;; mass
|
rlm@320
|
280 1.0 ;; friction
|
rlm@320
|
281 ;; texture
|
rlm@320
|
282 "Textures/Terrain/BrickWall/BrickWall.jpg"
|
rlm@320
|
283 ;; material
|
rlm@320
|
284 "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@320
|
285 Vector3f/ZERO
|
rlm@320
|
286 Quaternion/IDENTITY
|
rlm@320
|
287 (Box. Vector3f/ZERO 0.5 0.5 0.5)
|
rlm@320
|
288 true
|
rlm@320
|
289 false))
|
rlm@25
|
290
|
rlm@25
|
291 (defn make-shape
|
rlm@25
|
292 [#^shape-description d]
|
rlm@29
|
293 (let [asset-manager (asset-manager)
|
rlm@25
|
294 mat (Material. asset-manager (:material d))
|
rlm@25
|
295 geom (Geometry. (:name d) (:shape d))]
|
rlm@25
|
296 (if (:texture d)
|
rlm@25
|
297 (let [key (TextureKey. (:texture d))]
|
rlm@363
|
298 (.setGenerateMips key true)
|
rlm@363
|
299 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))
|
rlm@34
|
300 ))
|
rlm@25
|
301 (if (:color d) (.setColor mat "Color" (:color d)))
|
rlm@25
|
302 (.setMaterial geom mat)
|
rlm@25
|
303 (if-let [rotation (:rotation d)] (.rotate geom rotation))
|
rlm@25
|
304 (.setLocalTranslation geom (:position d))
|
rlm@25
|
305 (if (:physical? d)
|
rlm@29
|
306 (let [physics-control
|
rlm@29
|
307 (if (:GImpact d)
|
rlm@29
|
308 ;; Create an accurate mesh collision shape if desired.
|
rlm@29
|
309 (RigidBodyControl.
|
rlm@29
|
310 (doto (GImpactCollisionShape.
|
rlm@29
|
311 (.getMesh geom))
|
rlm@29
|
312 (.createJmeMesh)
|
rlm@73
|
313 ;;(.setMargin 0)
|
rlm@73
|
314 )
|
rlm@29
|
315 (float (:mass d)))
|
rlm@29
|
316 ;; otherwise use jme3's default
|
rlm@29
|
317 (RigidBodyControl. (float (:mass d))))]
|
rlm@25
|
318 (.addControl geom physics-control)
|
rlm@25
|
319 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
|
rlm@25
|
320 (.setFriction physics-control (:friction d))))
|
rlm@25
|
321 geom))
|
rlm@25
|
322
|
rlm@25
|
323 (defn box
|
rlm@25
|
324 ([l w h & {:as options}]
|
rlm@25
|
325 (let [options (merge base-shape options)]
|
rlm@25
|
326 (make-shape (assoc options
|
rlm@25
|
327 :shape (Box. l w h)))))
|
rlm@25
|
328 ([] (box 0.5 0.5 0.5)))
|
rlm@25
|
329
|
rlm@25
|
330 (defn sphere
|
rlm@25
|
331 ([r & {:as options}]
|
rlm@25
|
332 (let [options (merge base-shape options)]
|
rlm@25
|
333 (make-shape (assoc options
|
rlm@25
|
334 :shape (Sphere. 32 32 (float r))))))
|
rlm@25
|
335 ([] (sphere 0.5)))
|
rlm@62
|
336
|
rlm@192
|
337 (defn x-ray
|
rlm@306
|
338 "A useful material for debugging -- it can be seen no matter what
|
rlm@306
|
339 object occludes it."
|
rlm@192
|
340 [#^ColorRGBA color]
|
rlm@192
|
341 (doto (Material. (asset-manager)
|
rlm@192
|
342 "Common/MatDefs/Misc/Unshaded.j3md")
|
rlm@192
|
343 (.setColor "Color" color)
|
rlm@192
|
344 (-> (.getAdditionalRenderState)
|
rlm@192
|
345 (.setDepthTest false))))
|
rlm@74
|
346
|
rlm@62
|
347 (defn node-seq
|
rlm@62
|
348 "Take a node and return a seq of all its children
|
rlm@62
|
349 recursively. There will be no nodes left in the resulting
|
rlm@62
|
350 structure"
|
rlm@62
|
351 [#^Node node]
|
rlm@62
|
352 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
|
rlm@62
|
353
|
rlm@62
|
354 (defn nodify
|
rlm@62
|
355 "Take a sequence of things that can be attached to a node and return
|
rlm@62
|
356 a node with all of them attached"
|
rlm@62
|
357 ([name children]
|
rlm@62
|
358 (let [node (Node. name)]
|
rlm@62
|
359 (dorun (map #(.attachChild node %) children))
|
rlm@62
|
360 node))
|
rlm@62
|
361 ([children] (nodify "" children)))
|
rlm@62
|
362
|
rlm@192
|
363 (defn load-blender-model
|
rlm@192
|
364 "Load a .blend file using an asset folder relative path."
|
rlm@192
|
365 [^String model]
|
rlm@192
|
366 (.loadModel
|
rlm@192
|
367 (doto (asset-manager)
|
rlm@192
|
368 (.registerLoader BlenderModelLoader
|
rlm@192
|
369 (into-array String ["blend"]))) model))
|
rlm@192
|
370
|
rlm@62
|
371
|
rlm@458
|
372
|
rlm@458
|
373 (def brick-length 0.48)
|
rlm@458
|
374 (def brick-width 0.24)
|
rlm@458
|
375 (def brick-height 0.12)
|
rlm@458
|
376 (def gravity (Vector3f. 0 -9.81 0))
|
rlm@458
|
377
|
rlm@458
|
378 (import com.jme3.math.Vector2f)
|
rlm@458
|
379 (import com.jme3.renderer.queue.RenderQueue$ShadowMode)
|
rlm@458
|
380 (import com.jme3.texture.Texture$WrapMode)
|
rlm@458
|
381
|
rlm@458
|
382 (defn brick* [position]
|
rlm@458
|
383 (println "get brick.")
|
rlm@458
|
384 (doto (box brick-length brick-height brick-width
|
rlm@458
|
385 :position position :name "brick"
|
rlm@458
|
386 :material "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@458
|
387 :texture "Textures/Terrain/BrickWall/BrickWall.jpg"
|
rlm@458
|
388 :mass 34)
|
rlm@458
|
389 (->
|
rlm@458
|
390 (.getMesh)
|
rlm@458
|
391 (.scaleTextureCoordinates (Vector2f. 1 0.5)))
|
rlm@458
|
392 (.setShadowMode RenderQueue$ShadowMode/CastAndReceive)
|
rlm@458
|
393 )
|
rlm@458
|
394 )
|
rlm@458
|
395
|
rlm@458
|
396
|
rlm@458
|
397 (defn floor*
|
rlm@458
|
398 "make a sturdy, unmovable physical floor"
|
rlm@458
|
399 []
|
rlm@458
|
400 (box 10 0.1 5 :name "floor" :mass 0
|
rlm@458
|
401 :color ColorRGBA/Gray :position (Vector3f. 0 0 0)))
|
rlm@458
|
402
|
rlm@458
|
403 (defn floor* []
|
rlm@458
|
404 (doto (box 10 0.1 5 :name "floor" ;10 0.1 5 ; 240 0.1 240
|
rlm@458
|
405 :material "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@458
|
406 :texture "Textures/BronzeCopper030.jpg"
|
rlm@458
|
407 :position (Vector3f. 0 0 0 )
|
rlm@458
|
408 :mass 0)
|
rlm@458
|
409 (->
|
rlm@458
|
410 (.getMesh)
|
rlm@458
|
411 (.scaleTextureCoordinates (Vector2f. 3 6)));64 64
|
rlm@458
|
412 (->
|
rlm@458
|
413 (.getMaterial)
|
rlm@458
|
414 (.getTextureParam "ColorMap")
|
rlm@458
|
415 (.getTextureValue)
|
rlm@458
|
416 (.setWrap Texture$WrapMode/Repeat))
|
rlm@458
|
417 (.setShadowMode RenderQueue$ShadowMode/Receive)
|
rlm@458
|
418 ))
|
rlm@458
|
419
|
rlm@458
|
420
|
rlm@458
|
421 (defn brick-wall* []
|
rlm@458
|
422 (let [node (Node. "brick-wall")]
|
rlm@458
|
423 (dorun
|
rlm@458
|
424 (map
|
rlm@458
|
425 (comp #(.attachChild node %) brick*)
|
rlm@458
|
426 (for [y (range 10)
|
rlm@458
|
427 x (range 4)
|
rlm@458
|
428 z (range 1)]
|
rlm@458
|
429 (Vector3f.
|
rlm@458
|
430 (+ (* 2 x brick-length)
|
rlm@458
|
431 (if (even? (+ y z))
|
rlm@458
|
432 (/ brick-length 4) (/ brick-length -4)))
|
rlm@458
|
433 (+ (* brick-height (inc (* 2 y))))
|
rlm@458
|
434 (* 2 z brick-width) ))))
|
rlm@458
|
435 (.setShadowMode node RenderQueue$ShadowMode/CastAndReceive)
|
rlm@458
|
436 node))
|
rlm@458
|
437
|
rlm@458
|
438
|
rlm@29
|
439 #+end_src
|
rlm@25
|
440
|
rlm@25
|
441
|
rlm@50
|
442 *** Debug Actions
|
rlm@66
|
443 #+name: debug-actions
|
rlm@29
|
444 #+begin_src clojure :results silent
|
rlm@60
|
445 (in-ns 'cortex.util)
|
rlm@60
|
446
|
rlm@47
|
447 (defn basic-light-setup
|
rlm@306
|
448 "returns a sequence of lights appropriate for fully lighting a scene"
|
rlm@47
|
449 []
|
rlm@47
|
450 (conj
|
rlm@47
|
451 (doall
|
rlm@47
|
452 (map
|
rlm@47
|
453 (fn [direction]
|
rlm@47
|
454 (doto (DirectionalLight.)
|
rlm@47
|
455 (.setDirection direction)
|
rlm@47
|
456 (.setColor ColorRGBA/White)))
|
rlm@47
|
457 [;; six faces of a cube
|
rlm@47
|
458 Vector3f/UNIT_X
|
rlm@47
|
459 Vector3f/UNIT_Y
|
rlm@47
|
460 Vector3f/UNIT_Z
|
rlm@47
|
461 (.mult Vector3f/UNIT_X (float -1))
|
rlm@47
|
462 (.mult Vector3f/UNIT_Y (float -1))
|
rlm@47
|
463 (.mult Vector3f/UNIT_Z (float -1))]))
|
rlm@47
|
464 (doto (AmbientLight.)
|
rlm@47
|
465 (.setColor ColorRGBA/White))))
|
rlm@47
|
466
|
rlm@47
|
467 (defn light-up-everything
|
rlm@306
|
468 "Add lights to a world appropriate for quickly seeing everything
|
rlm@47
|
469 in the scene. Adds six DirectionalLights facing in orthogonal
|
rlm@47
|
470 directions, and one AmbientLight to provide overall lighting
|
rlm@47
|
471 coverage."
|
rlm@47
|
472 [world]
|
rlm@47
|
473 (dorun
|
rlm@47
|
474 (map
|
rlm@47
|
475 #(.addLight (.getRootNode world) %)
|
rlm@47
|
476 (basic-light-setup))))
|
rlm@50
|
477
|
rlm@50
|
478 (defn fire-cannon-ball
|
rlm@50
|
479 "Creates a function that fires a cannon-ball from the current game's
|
rlm@50
|
480 camera. The cannon-ball will be attached to the node if provided, or
|
rlm@50
|
481 to the game's RootNode if no node is provided."
|
rlm@50
|
482 ([node]
|
rlm@50
|
483 (fn [game value]
|
rlm@50
|
484 (if (not value)
|
rlm@50
|
485 (let [camera (.getCamera game)
|
rlm@50
|
486 cannon-ball
|
rlm@363
|
487 (sphere 0.4
|
rlm@363
|
488 ;;:texture nil
|
rlm@50
|
489 :material "Common/MatDefs/Misc/Unshaded.j3md"
|
rlm@363
|
490 :color ColorRGBA/Blue
|
rlm@216
|
491 :name "cannonball!"
|
rlm@50
|
492 :position
|
rlm@50
|
493 (.add (.getLocation camera)
|
rlm@50
|
494 (.mult (.getDirection camera) (float 1)))
|
rlm@363
|
495 :mass 25)] ;200 0.05
|
rlm@50
|
496 (.setLinearVelocity
|
rlm@50
|
497 (.getControl cannon-ball RigidBodyControl)
|
rlm@50
|
498 (.mult (.getDirection camera) (float 50))) ;50
|
rlm@216
|
499 (add-element game cannon-ball (if node node (.getRootNode
|
rlm@216
|
500 game)))
|
rlm@216
|
501 cannon-ball))))
|
rlm@50
|
502 ([]
|
rlm@50
|
503 (fire-cannon-ball false)))
|
rlm@60
|
504
|
rlm@60
|
505 (def standard-debug-controls
|
rlm@60
|
506 {"key-space" (fire-cannon-ball)})
|
rlm@60
|
507
|
rlm@60
|
508
|
rlm@160
|
509 (defn tap [obj direction force]
|
rlm@160
|
510 (let [control (.getControl obj RigidBodyControl)]
|
rlm@160
|
511 (.applyTorque
|
rlm@160
|
512 control
|
rlm@160
|
513 (.mult (.getPhysicsRotation control)
|
rlm@160
|
514 (.mult (.normalize direction) (float force))))))
|
rlm@160
|
515
|
rlm@160
|
516
|
rlm@160
|
517 (defn with-movement
|
rlm@160
|
518 [object
|
rlm@160
|
519 [up down left right roll-up roll-down :as keyboard]
|
rlm@160
|
520 forces
|
rlm@160
|
521 [root-node
|
rlm@160
|
522 keymap
|
rlm@306
|
523 initialization
|
rlm@160
|
524 world-loop]]
|
rlm@160
|
525 (let [add-keypress
|
rlm@160
|
526 (fn [state keymap key]
|
rlm@160
|
527 (merge keymap
|
rlm@160
|
528 {key
|
rlm@160
|
529 (fn [_ pressed?]
|
rlm@160
|
530 (reset! state pressed?))}))
|
rlm@160
|
531 move-up? (atom false)
|
rlm@160
|
532 move-down? (atom false)
|
rlm@160
|
533 move-left? (atom false)
|
rlm@160
|
534 move-right? (atom false)
|
rlm@160
|
535 roll-left? (atom false)
|
rlm@160
|
536 roll-right? (atom false)
|
rlm@160
|
537
|
rlm@160
|
538 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
|
rlm@160
|
539 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
|
rlm@160
|
540 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
|
rlm@160
|
541 atoms [move-left? move-right? move-up? move-down?
|
rlm@160
|
542 roll-left? roll-right?]
|
rlm@160
|
543
|
rlm@160
|
544 keymap* (reduce merge
|
rlm@160
|
545 (map #(add-keypress %1 keymap %2)
|
rlm@160
|
546 atoms
|
rlm@160
|
547 keyboard))
|
rlm@160
|
548
|
rlm@160
|
549 splice-loop (fn []
|
rlm@160
|
550 (dorun
|
rlm@160
|
551 (map
|
rlm@160
|
552 (fn [sym direction force]
|
rlm@160
|
553 (if @sym
|
rlm@160
|
554 (tap object direction force)))
|
rlm@160
|
555 atoms directions forces)))
|
rlm@160
|
556
|
rlm@160
|
557 world-loop* (fn [world tpf]
|
rlm@160
|
558 (world-loop world tpf)
|
rlm@160
|
559 (splice-loop))]
|
rlm@160
|
560 [root-node
|
rlm@160
|
561 keymap*
|
rlm@306
|
562 initialization
|
rlm@160
|
563 world-loop*]))
|
rlm@160
|
564
|
rlm@205
|
565 (import com.jme3.font.BitmapText)
|
rlm@205
|
566 (import com.jme3.scene.control.AbstractControl)
|
rlm@205
|
567 (import com.aurellem.capture.IsoTimer)
|
rlm@60
|
568
|
rlm@306
|
569 (defn display-dilated-time
|
rlm@205
|
570 "Shows the time as it is flowing in the simulation on a HUD display.
|
rlm@205
|
571 Useful for making videos."
|
rlm@205
|
572 [world timer]
|
rlm@205
|
573 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
|
rlm@205
|
574 text (BitmapText. font false)]
|
rlm@205
|
575 (.setLocalTranslation text 300 (.getLineHeight text) 0)
|
rlm@205
|
576 (.addControl
|
rlm@205
|
577 text
|
rlm@205
|
578 (proxy [AbstractControl] []
|
rlm@205
|
579 (controlUpdate [tpf]
|
rlm@205
|
580 (.setText text (format
|
rlm@205
|
581 "%.2f"
|
rlm@341
|
582 (float (.getTimeInSeconds timer)))))
|
rlm@205
|
583 (controlRender [_ _])))
|
rlm@205
|
584 (.attachChild (.getGuiNode world) text)))
|
rlm@50
|
585 #+end_src
|
rlm@50
|
586
|
rlm@50
|
587
|
rlm@50
|
588 *** Viewing Objects
|
rlm@50
|
589
|
rlm@66
|
590 #+name: world-view
|
rlm@50
|
591 #+begin_src clojure :results silent
|
rlm@50
|
592 (in-ns 'cortex.util)
|
rlm@50
|
593
|
rlm@50
|
594 (defprotocol Viewable
|
rlm@50
|
595 (view [something]))
|
rlm@50
|
596
|
rlm@50
|
597 (extend-type com.jme3.scene.Geometry
|
rlm@50
|
598 Viewable
|
rlm@50
|
599 (view [geo]
|
rlm@50
|
600 (view (doto (Node.)(.attachChild geo)))))
|
rlm@47
|
601
|
rlm@29
|
602 (extend-type com.jme3.scene.Node
|
rlm@29
|
603 Viewable
|
rlm@29
|
604 (view
|
rlm@29
|
605 [node]
|
rlm@29
|
606 (.start
|
rlm@29
|
607 (world
|
rlm@29
|
608 node
|
rlm@29
|
609 {}
|
rlm@29
|
610 (fn [world]
|
rlm@29
|
611 (enable-debug world)
|
rlm@29
|
612 (set-gravity world Vector3f/ZERO)
|
rlm@47
|
613 (light-up-everything world))
|
rlm@29
|
614 no-op))))
|
rlm@62
|
615
|
rlm@62
|
616 (extend-type com.jme3.math.ColorRGBA
|
rlm@62
|
617 Viewable
|
rlm@62
|
618 (view
|
rlm@62
|
619 [color]
|
rlm@62
|
620 (view (doto (Node.)
|
rlm@62
|
621 (.attachChild (box 1 1 1 :color color))))))
|
rlm@62
|
622
|
rlm@336
|
623 (extend-type ij.ImagePlus
|
rlm@336
|
624 Viewable
|
rlm@336
|
625 (view [image]
|
rlm@336
|
626 (.show image)))
|
rlm@336
|
627
|
rlm@335
|
628 (extend-type java.awt.image.BufferedImage
|
rlm@335
|
629 Viewable
|
rlm@335
|
630 (view
|
rlm@335
|
631 [image]
|
rlm@336
|
632 (view (ImagePlus. "view-buffered-image" image))))
|
rlm@336
|
633
|
rlm@336
|
634
|
rlm@62
|
635 (defprotocol Textual
|
rlm@62
|
636 (text [something]
|
rlm@62
|
637 "Display a detailed textual analysis of the given object."))
|
rlm@62
|
638
|
rlm@62
|
639 (extend-type com.jme3.scene.Node
|
rlm@62
|
640 Textual
|
rlm@62
|
641 (text [node]
|
rlm@62
|
642 (println "Total Vertexes: " (.getVertexCount node))
|
rlm@62
|
643 (println "Total Triangles: " (.getTriangleCount node))
|
rlm@62
|
644 (println "Controls :")
|
rlm@62
|
645 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
|
rlm@62
|
646 (println "Has " (.getQuantity node) " Children:")
|
rlm@62
|
647 (doall (map text (.getChildren node)))))
|
rlm@62
|
648
|
rlm@62
|
649 (extend-type com.jme3.animation.AnimControl
|
rlm@62
|
650 Textual
|
rlm@62
|
651 (text [control]
|
rlm@62
|
652 (let [animations (.getAnimationNames control)]
|
rlm@62
|
653 (println "Animation Control with " (count animations) " animation(s):")
|
rlm@62
|
654 (dorun (map println animations)))))
|
rlm@62
|
655
|
rlm@62
|
656 (extend-type com.jme3.animation.SkeletonControl
|
rlm@62
|
657 Textual
|
rlm@62
|
658 (text [control]
|
rlm@62
|
659 (println "Skeleton Control with the following skeleton:")
|
rlm@62
|
660 (println (.getSkeleton control))))
|
rlm@62
|
661
|
rlm@62
|
662 (extend-type com.jme3.bullet.control.KinematicRagdollControl
|
rlm@62
|
663 Textual
|
rlm@62
|
664 (text [control]
|
rlm@62
|
665 (println "Ragdoll Control")))
|
rlm@62
|
666
|
rlm@62
|
667 (extend-type com.jme3.scene.Geometry
|
rlm@62
|
668 Textual
|
rlm@62
|
669 (text [control]
|
rlm@62
|
670 (println "...geo...")))
|
rlm@108
|
671
|
rlm@108
|
672 (extend-type Triangle
|
rlm@108
|
673 Textual
|
rlm@108
|
674 (text [t]
|
rlm@108
|
675 (println "Triangle: " \newline (.get1 t) \newline
|
rlm@108
|
676 (.get2 t) \newline (.get3 t))))
|
rlm@108
|
677
|
rlm@25
|
678 #+end_src
|
rlm@25
|
679
|
rlm@29
|
680 Here I make the =Viewable= protocol and extend it to JME's types. Now
|
rlm@34
|
681 JME3's =hello-world= can be written as easily as:
|
rlm@29
|
682
|
rlm@29
|
683 #+begin_src clojure :results silent
|
rlm@29
|
684 (cortex.util/view (cortex.util/box))
|
rlm@29
|
685 #+end_src
|
rlm@29
|
686
|
rlm@29
|
687
|
rlm@400
|
688 * code generation
|
rlm@24
|
689 #+begin_src clojure :tangle ../src/cortex/import.clj
|
rlm@24
|
690 <<import>>
|
rlm@24
|
691 #+end_src
|
rlm@25
|
692
|
rlm@25
|
693
|
rlm@29
|
694 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
|
rlm@25
|
695 <<util>>
|
rlm@25
|
696 <<shapes>>
|
rlm@50
|
697 <<debug-actions>>
|
rlm@29
|
698 <<world-view>>
|
rlm@25
|
699 #+end_src
|
rlm@25
|
700
|
rlm@29
|
701
|
rlm@29
|
702
|
rlm@29
|
703
|
rlm@29
|
704
|
rlm@29
|
705
|
rlm@29
|
706
|
rlm@29
|
707
|
rlm@29
|
708
|
rlm@29
|
709
|
rlm@29
|
710
|
rlm@29
|
711
|
rlm@29
|
712
|
rlm@29
|
713
|
rlm@29
|
714
|
rlm@29
|
715
|
rlm@29
|
716
|