view org/util.org @ 95:e4bcd0c481ba

now with more accurate authorship
author Robert McIntyre <rlm@mit.edu>
date Tue, 10 Jan 2012 08:38:50 -0700
parents 77b506ac64f3
children 92b857b6145d
line wrap: on
line source
1 #+title: Clojure Utilities for jMonkeyEngine3
2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu
4 #+description:
5 #+keywords: JME3, clojure, import, utilities
6 #+SETUPFILE: ../../aurellem/org/setup.org
7 #+INCLUDE: ../../aurellem/org/level-0.org
9 [TABLE-OF-CONTENTS]
11 These are a collection of functions to make programming jMonkeyEngine
12 in clojure easier.
14 * Imports
16 #+name: import
17 #+begin_src clojure :results silent
18 (ns cortex.import
19 (:require swank.util.class-browse))
21 (defn permissive-import
22 [classname]
23 (eval `(try (import '~classname)
24 (catch java.lang.Exception e#
25 (println "couldn't import " '~classname))))
26 classname)
28 (defn jme-class? [classname]
29 (and
30 (.startsWith classname "com.jme3.")
31 ;; Don't import the Lwjgl stuff since it can throw exceptions
32 ;; upon being loaded.
33 (not (re-matches #".*Lwjgl.*" classname))))
35 (defn jme-classes
36 "returns a list of all jme3 classes"
37 []
38 (filter
39 jme-class?
40 (map :name
41 swank.util.class-browse/available-classes)))
43 (defn mega-import-jme3
44 "Import ALL the jme classes. For REPL use."
45 []
46 (doall
47 (map (comp permissive-import symbol) (jme-classes))))
48 #+end_src
50 jMonkeyEngine3 has a plethora of classes which can be overwhelming to
51 manage. This code uses reflection to import all of them. Once I'm
52 happy with the general structure of a namespace I can deal with
53 importing only the classes it actually needs.
55 The =mega-import-jme3= is quite usefull for debugging purposes since
56 it allows completion for almost all of JME's classes from the REPL.
58 Out of curiousity, let's see just how many classes =mega-import-jme3=
59 imports:
61 #+begin_src clojure :exports both :results output
62 (println (clojure.core/count (cortex.import/jme-classes)) "classes")
63 #+end_src
65 #+results:
66 : 955 classes
69 * Utilities
71 The utilities here come in three main groups:
72 - Changing settings in a running =Application=
73 - Creating objects
74 - Debug Actions
75 - Visualizing objects
77 *** Changing Settings
79 #+name: util
80 #+begin_src clojure
81 (ns cortex.util
82 "Utility functions for making jMonkeyEngine3 easier to program from
83 clojure."
84 {:author "Robert McIntyre"}
85 (:use cortex.world)
86 (:use clojure.contrib.def)
87 (:import com.jme3.math.Vector3f)
88 (:import com.jme3.math.Quaternion)
89 (:import com.jme3.asset.TextureKey)
90 (:import com.jme3.bullet.control.RigidBodyControl)
91 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
92 (:import com.jme3.scene.shape.Box)
93 (:import com.jme3.scene.Node)
94 (:import com.jme3.scene.shape.Sphere)
95 (:import com.jme3.light.AmbientLight)
96 (:import com.jme3.light.DirectionalLight)
97 (:import com.jme3.math.ColorRGBA)
98 (:import com.jme3.bullet.BulletAppState)
99 (:import com.jme3.material.Material)
100 (:import com.jme3.scene.Geometry)
101 (:import (java.util.logging Level Logger)))
105 (defvar println-repl
106 (bound-fn [& args] (apply println args))
107 "println called from the LWJGL thread will not go to the REPL, but
108 instead to whatever terminal started the JVM process. This function
109 will always output to the REPL")
111 (defn position-camera
112 "Change the position of the in-world camera."
113 ([world position direction up]
114 (doto (.getCamera world)
115 (.setLocation )
116 (.lookAt direction up)))
117 ([world position direction]
118 (position-camera
119 world position direction Vector3f/UNIT_Y)))
121 (defn enable-debug
122 "Turn on debug wireframes for every object in this simulation."
123 [world]
124 (.enableDebug
125 (.getPhysicsSpace
126 (.getState
127 (.getStateManager world)
128 BulletAppState))
129 (asset-manager)))
131 (defn speed-up
132 "Increase the dismally slow speed of the world's camera."
133 [world]
134 (.setMoveSpeed (.getFlyByCamera world)
135 (float 100))
136 (.setRotationSpeed (.getFlyByCamera world)
137 (float 20))
138 world)
141 (defn no-logging
142 "Disable all of jMonkeyEngine's logging."
143 []
144 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
146 (defn set-accuracy
147 "Change the accuracy at which the World's Physics is calculated."
148 [world new-accuracy]
149 (let [physics-manager
150 (.getState
151 (.getStateManager world) BulletAppState)]
152 (.setAccuracy
153 (.getPhysicsSpace physics-manager)
154 (float new-accuracy))))
157 (defn set-gravity
158 "In order to change the gravity of a scene, it is not only necessary
159 to set the gravity variable, but to \"tap\" every physics object in
160 the scene to reactivate physics calculations."
161 [world gravity]
162 (traverse
163 (fn [geom]
164 (if-let
165 ;; only set gravity for physical objects.
166 [control (.getControl geom RigidBodyControl)]
167 (do
168 (.setGravity control gravity)
169 ;; tappsies!
170 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
171 (.getRootNode world)))
173 (defn add-element
174 "Add the Spatial to the world's environment"
175 ([world element node]
176 (.addAll
177 (.getPhysicsSpace
178 (.getState
179 (.getStateManager world)
180 BulletAppState))
181 element)
182 (.attachChild node element))
183 ([world element]
184 (add-element world element (.getRootNode world))))
186 (defn apply-map
187 "Like apply, but works for maps and functions that expect an
188 implicit map and nothing else as in (fn [& {}]).
189 ------- Example -------
190 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
191 (println www))
192 (apply-map demo {:www \"hello!\"})
193 -->\"hello\""
194 [fn m]
195 (apply fn (reduce #(into %1 %2) [] m)))
197 #+end_src
199 #+results: util
200 : #'cortex.util/apply-map
203 *** Creating Basic Shapes
205 #+name: shapes
206 #+begin_src clojure :results silent
207 (in-ns 'cortex.util)
209 (defrecord shape-description
210 [name
211 color
212 mass
213 friction
214 texture
215 material
216 position
217 rotation
218 shape
219 physical?
220 GImpact?
221 ])
223 (defvar base-shape
224 (shape-description.
225 "default-shape"
226 false
227 ;;ColorRGBA/Blue
228 1.0 ;; mass
229 1.0 ;; friction
230 ;; texture
231 "Textures/Terrain/BrickWall/BrickWall.jpg"
232 ;; material
233 "Common/MatDefs/Misc/Unshaded.j3md"
234 Vector3f/ZERO
235 Quaternion/IDENTITY
236 (Box. Vector3f/ZERO 0.5 0.5 0.5)
237 true
238 false)
239 "Basic settings for shapes.")
241 (defn make-shape
242 [#^shape-description d]
243 (let [asset-manager (asset-manager)
244 mat (Material. asset-manager (:material d))
245 geom (Geometry. (:name d) (:shape d))]
246 (if (:texture d)
247 (let [key (TextureKey. (:texture d))]
248 ;;(.setGenerateMips key true)
249 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
250 ))
251 (if (:color d) (.setColor mat "Color" (:color d)))
252 (.setMaterial geom mat)
253 (if-let [rotation (:rotation d)] (.rotate geom rotation))
254 (.setLocalTranslation geom (:position d))
255 (if (:physical? d)
256 (let [physics-control
257 (if (:GImpact d)
258 ;; Create an accurate mesh collision shape if desired.
259 (RigidBodyControl.
260 (doto (GImpactCollisionShape.
261 (.getMesh geom))
262 (.createJmeMesh)
263 ;;(.setMargin 0)
264 )
265 (float (:mass d)))
266 ;; otherwise use jme3's default
267 (RigidBodyControl. (float (:mass d))))]
268 (.addControl geom physics-control)
269 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
270 (.setFriction physics-control (:friction d))))
271 geom))
273 (defn box
274 ([l w h & {:as options}]
275 (let [options (merge base-shape options)]
276 (make-shape (assoc options
277 :shape (Box. l w h)))))
278 ([] (box 0.5 0.5 0.5)))
280 (defn sphere
281 ([r & {:as options}]
282 (let [options (merge base-shape options)]
283 (make-shape (assoc options
284 :shape (Sphere. 32 32 (float r))))))
285 ([] (sphere 0.5)))
287 (defn green-x-ray
288 "A usefull material for debuging -- it can be seen no matter what
289 object occuldes it."
290 []
291 (doto (Material. (asset-manager)
292 "Common/MatDefs/Misc/Unshaded.j3md")
293 (.setColor "Color" ColorRGBA/Green)
294 (-> (.getAdditionalRenderState)
295 (.setDepthTest false))))
297 (defn node-seq
298 "Take a node and return a seq of all its children
299 recursively. There will be no nodes left in the resulting
300 structure"
301 [#^Node node]
302 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
304 (defn nodify
305 "Take a sequence of things that can be attached to a node and return
306 a node with all of them attached"
307 ([name children]
308 (let [node (Node. name)]
309 (dorun (map #(.attachChild node %) children))
310 node))
311 ([children] (nodify "" children)))
314 #+end_src
317 *** Debug Actions
318 #+name: debug-actions
319 #+begin_src clojure :results silent
320 (in-ns 'cortex.util)
322 (defn basic-light-setup
323 "returns a sequence of lights appropiate for fully lighting a scene"
324 []
325 (conj
326 (doall
327 (map
328 (fn [direction]
329 (doto (DirectionalLight.)
330 (.setDirection direction)
331 (.setColor ColorRGBA/White)))
332 [;; six faces of a cube
333 Vector3f/UNIT_X
334 Vector3f/UNIT_Y
335 Vector3f/UNIT_Z
336 (.mult Vector3f/UNIT_X (float -1))
337 (.mult Vector3f/UNIT_Y (float -1))
338 (.mult Vector3f/UNIT_Z (float -1))]))
339 (doto (AmbientLight.)
340 (.setColor ColorRGBA/White))))
342 (defn light-up-everything
343 "Add lights to a world appropiate for quickly seeing everything
344 in the scene. Adds six DirectionalLights facing in orthogonal
345 directions, and one AmbientLight to provide overall lighting
346 coverage."
347 [world]
348 (dorun
349 (map
350 #(.addLight (.getRootNode world) %)
351 (basic-light-setup))))
353 (defn fire-cannon-ball
354 "Creates a function that fires a cannon-ball from the current game's
355 camera. The cannon-ball will be attached to the node if provided, or
356 to the game's RootNode if no node is provided."
357 ([node]
358 (fn [game value]
359 (if (not value)
360 (let [camera (.getCamera game)
361 cannon-ball
362 (sphere 0.7
363 :material "Common/MatDefs/Misc/Unshaded.j3md"
364 :texture "Textures/PokeCopper.jpg"
365 :position
366 (.add (.getLocation camera)
367 (.mult (.getDirection camera) (float 1)))
368 :mass 3)] ;200 0.05
369 (.setLinearVelocity
370 (.getControl cannon-ball RigidBodyControl)
371 (.mult (.getDirection camera) (float 50))) ;50
372 (add-element game cannon-ball (if node node (.getRootNode game)))))))
373 ([]
374 (fire-cannon-ball false)))
376 (def standard-debug-controls
377 {"key-space" (fire-cannon-ball)})
382 #+end_src
385 *** Viewing Objects
387 #+name: world-view
388 #+begin_src clojure :results silent
389 (in-ns 'cortex.util)
391 (defprotocol Viewable
392 (view [something]))
394 (extend-type com.jme3.scene.Geometry
395 Viewable
396 (view [geo]
397 (view (doto (Node.)(.attachChild geo)))))
399 (extend-type com.jme3.scene.Node
400 Viewable
401 (view
402 [node]
403 (.start
404 (world
405 node
406 {}
407 (fn [world]
408 (enable-debug world)
409 (set-gravity world Vector3f/ZERO)
410 (light-up-everything world))
411 no-op))))
413 (extend-type com.jme3.math.ColorRGBA
414 Viewable
415 (view
416 [color]
417 (view (doto (Node.)
418 (.attachChild (box 1 1 1 :color color))))))
420 (defprotocol Textual
421 (text [something]
422 "Display a detailed textual analysis of the given object."))
424 (extend-type com.jme3.scene.Node
425 Textual
426 (text [node]
427 (println "Total Vertexes: " (.getVertexCount node))
428 (println "Total Triangles: " (.getTriangleCount node))
429 (println "Controls :")
430 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
431 (println "Has " (.getQuantity node) " Children:")
432 (doall (map text (.getChildren node)))))
434 (extend-type com.jme3.animation.AnimControl
435 Textual
436 (text [control]
437 (let [animations (.getAnimationNames control)]
438 (println "Animation Control with " (count animations) " animation(s):")
439 (dorun (map println animations)))))
441 (extend-type com.jme3.animation.SkeletonControl
442 Textual
443 (text [control]
444 (println "Skeleton Control with the following skeleton:")
445 (println (.getSkeleton control))))
447 (extend-type com.jme3.bullet.control.KinematicRagdollControl
448 Textual
449 (text [control]
450 (println "Ragdoll Control")))
452 (extend-type com.jme3.scene.Geometry
453 Textual
454 (text [control]
455 (println "...geo...")))
456 #+end_src
458 Here I make the =Viewable= protocol and extend it to JME's types. Now
459 JME3's =hello-world= can be written as easily as:
461 #+begin_src clojure :results silent
462 (cortex.util/view (cortex.util/box))
463 #+end_src
466 * COMMENT code generation
467 #+begin_src clojure :tangle ../src/cortex/import.clj
468 <<import>>
469 #+end_src
472 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
473 <<util>>
474 <<shapes>>
475 <<debug-actions>>
476 <<world-view>>
477 #+end_src