view org/util.org @ 69:39e4e1542e4a

updated test-suite
author Robert McIntyre <rlm@mit.edu>
date Fri, 09 Dec 2011 23:11:28 -0600
parents 1381a6ebd08b
children 0235c32152af
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
79 *** Changing Settings
81 #+name: util
82 #+begin_src clojure
83 (ns cortex.util
84 "Utility functions for making jMonkeyEngine3 easier to program from
85 clojure."
86 {:author "Robert McIntyre"}
87 (:use cortex.world)
88 (:use clojure.contrib.def)
89 (:import com.jme3.math.Vector3f)
90 (:import com.jme3.math.Quaternion)
91 (:import com.jme3.asset.TextureKey)
92 (:import com.jme3.bullet.control.RigidBodyControl)
93 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
94 (:import com.jme3.scene.shape.Box)
95 (:import com.jme3.scene.Node)
96 (:import com.jme3.scene.shape.Sphere)
97 (:import com.jme3.light.AmbientLight)
98 (:import com.jme3.light.DirectionalLight)
99 (:import com.jme3.math.ColorRGBA)
100 (:import com.jme3.bullet.BulletAppState)
101 (:import com.jme3.material.Material)
102 (:import com.jme3.scene.Geometry)
103 (:import (java.util.logging Level Logger)))
107 (defvar println-repl
108 (bound-fn [& args] (apply println args))
109 "println called from the LWJGL thread will not go to the REPL, but
110 instead to whatever terminal started the JVM process. This function
111 will always output to the REPL")
113 (defn position-camera
114 "Change the position of the in-world camera."
115 ([world position direction up]
116 (doto (.getCamera world)
117 (.setLocation )
118 (.lookAt direction up)))
119 ([world position direction]
120 (position-camera
121 world position direction Vector3f/UNIT_Y)))
123 (defn enable-debug
124 "Turn on debug wireframes for every object in this simulation."
125 [world]
126 (.enableDebug
127 (.getPhysicsSpace
128 (.getState
129 (.getStateManager world)
130 BulletAppState))
131 (asset-manager)))
133 (defn no-logging
134 "Disable all of jMonkeyEngine's logging."
135 []
136 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
138 (defn set-accuracy
139 "Change the accuracy at which the World's Physics is calculated."
140 [world new-accuracy]
141 (let [physics-manager
142 (.getState
143 (.getStateManager world) BulletAppState)]
144 (.setAccuracy
145 (.getPhysicsSpace physics-manager)
146 (float new-accuracy))))
149 (defn set-gravity
150 "In order to change the gravity of a scene, it is not only necessary
151 to set the gravity variable, but to \"tap\" every physics object in
152 the scene to reactivate physics calculations."
153 [world gravity]
154 (traverse
155 (fn [geom]
156 (if-let
157 ;; only set gravity for physical objects.
158 [control (.getControl geom RigidBodyControl)]
159 (do
160 (.setGravity control gravity)
161 ;; tappsies!
162 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
163 (.getRootNode world)))
165 (defn add-element
166 "Add the Spatial to the world's environment"
167 ([world element node]
168 (.addAll
169 (.getPhysicsSpace
170 (.getState
171 (.getStateManager world)
172 BulletAppState))
173 element)
174 (.attachChild node element))
175 ([world element]
176 (add-element world element (.getRootNode world))))
178 (defn apply-map
179 "Like apply, but works for maps and functions that expect an
180 implicit map and nothing else as in (fn [& {}]).
181 ------- Example -------
182 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
183 (println www))
184 (apply-map demo {:www \"hello!\"})
185 -->\"hello\""
186 [fn m]
187 (apply fn (reduce #(into %1 %2) [] m)))
189 #+end_src
191 #+results: util
192 : #'cortex.util/apply-map
195 *** Creating Basic Shapes
197 #+name: shapes
198 #+begin_src clojure :results silent
199 (in-ns 'cortex.util)
201 (defrecord shape-description
202 [name
203 color
204 mass
205 friction
206 texture
207 material
208 position
209 rotation
210 shape
211 physical?
212 GImpact?
213 ])
215 (defvar base-shape
216 (shape-description.
217 "default-shape"
218 false
219 ;;ColorRGBA/Blue
220 1.0 ;; mass
221 1.0 ;; friction
222 ;; texture
223 "Textures/Terrain/BrickWall/BrickWall.jpg"
224 ;; material
225 "Common/MatDefs/Misc/Unshaded.j3md"
226 Vector3f/ZERO
227 Quaternion/IDENTITY
228 (Box. Vector3f/ZERO 0.5 0.5 0.5)
229 true
230 false)
231 "Basic settings for shapes.")
233 (defn make-shape
234 [#^shape-description d]
235 (let [asset-manager (asset-manager)
236 mat (Material. asset-manager (:material d))
237 geom (Geometry. (:name d) (:shape d))]
238 (if (:texture d)
239 (let [key (TextureKey. (:texture d))]
240 ;;(.setGenerateMips key true)
241 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
242 ))
243 (if (:color d) (.setColor mat "Color" (:color d)))
244 (.setMaterial geom mat)
245 (if-let [rotation (:rotation d)] (.rotate geom rotation))
246 (.setLocalTranslation geom (:position d))
247 (if (:physical? d)
248 (let [physics-control
249 (if (:GImpact d)
250 ;; Create an accurate mesh collision shape if desired.
251 (RigidBodyControl.
252 (doto (GImpactCollisionShape.
253 (.getMesh geom))
254 (.createJmeMesh)
255 (.setMargin 0))
256 (float (:mass d)))
257 ;; otherwise use jme3's default
258 (RigidBodyControl. (float (:mass d))))]
259 (.addControl geom physics-control)
260 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
261 (.setFriction physics-control (:friction d))))
262 geom))
264 (defn box
265 ([l w h & {:as options}]
266 (let [options (merge base-shape options)]
267 (make-shape (assoc options
268 :shape (Box. l w h)))))
269 ([] (box 0.5 0.5 0.5)))
271 (defn sphere
272 ([r & {:as options}]
273 (let [options (merge base-shape options)]
274 (make-shape (assoc options
275 :shape (Sphere. 32 32 (float r))))))
276 ([] (sphere 0.5)))
278 (defn node-seq
279 "Take a node and return a seq of all its children
280 recursively. There will be no nodes left in the resulting
281 structure"
282 [#^Node node]
283 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
285 (defn nodify
286 "Take a sequence of things that can be attached to a node and return
287 a node with all of them attached"
288 ([name children]
289 (let [node (Node. name)]
290 (dorun (map #(.attachChild node %) children))
291 node))
292 ([children] (nodify "" children)))
295 #+end_src
298 *** Debug Actions
299 #+name: debug-actions
300 #+begin_src clojure :results silent
301 (in-ns 'cortex.util)
303 (defn basic-light-setup
304 "returns a sequence of lights appropiate for fully lighting a scene"
305 []
306 (conj
307 (doall
308 (map
309 (fn [direction]
310 (doto (DirectionalLight.)
311 (.setDirection direction)
312 (.setColor ColorRGBA/White)))
313 [;; six faces of a cube
314 Vector3f/UNIT_X
315 Vector3f/UNIT_Y
316 Vector3f/UNIT_Z
317 (.mult Vector3f/UNIT_X (float -1))
318 (.mult Vector3f/UNIT_Y (float -1))
319 (.mult Vector3f/UNIT_Z (float -1))]))
320 (doto (AmbientLight.)
321 (.setColor ColorRGBA/White))))
323 (defn light-up-everything
324 "Add lights to a world appropiate for quickly seeing everything
325 in the scene. Adds six DirectionalLights facing in orthogonal
326 directions, and one AmbientLight to provide overall lighting
327 coverage."
328 [world]
329 (dorun
330 (map
331 #(.addLight (.getRootNode world) %)
332 (basic-light-setup))))
334 (defn fire-cannon-ball
335 "Creates a function that fires a cannon-ball from the current game's
336 camera. The cannon-ball will be attached to the node if provided, or
337 to the game's RootNode if no node is provided."
338 ([node]
339 (fn [game value]
340 (if (not value)
341 (let [camera (.getCamera game)
342 cannon-ball
343 (sphere 0.7
344 :material "Common/MatDefs/Misc/Unshaded.j3md"
345 :texture "Textures/PokeCopper.jpg"
346 :position
347 (.add (.getLocation camera)
348 (.mult (.getDirection camera) (float 1)))
349 :mass 3)] ;200 0.05
350 (.setLinearVelocity
351 (.getControl cannon-ball RigidBodyControl)
352 (.mult (.getDirection camera) (float 50))) ;50
353 (add-element game cannon-ball (if node node (.getRootNode game)))))))
354 ([]
355 (fire-cannon-ball false)))
357 (def standard-debug-controls
358 {"key-space" (fire-cannon-ball)})
363 #+end_src
366 *** Viewing Objects
368 #+name: world-view
369 #+begin_src clojure :results silent
370 (in-ns 'cortex.util)
372 (defprotocol Viewable
373 (view [something]))
375 (extend-type com.jme3.scene.Geometry
376 Viewable
377 (view [geo]
378 (view (doto (Node.)(.attachChild geo)))))
380 (extend-type com.jme3.scene.Node
381 Viewable
382 (view
383 [node]
384 (.start
385 (world
386 node
387 {}
388 (fn [world]
389 (enable-debug world)
390 (set-gravity world Vector3f/ZERO)
391 (light-up-everything world))
392 no-op))))
394 (extend-type com.jme3.math.ColorRGBA
395 Viewable
396 (view
397 [color]
398 (view (doto (Node.)
399 (.attachChild (box 1 1 1 :color color))))))
401 (defprotocol Textual
402 (text [something]
403 "Display a detailed textual analysis of the given object."))
405 (extend-type com.jme3.scene.Node
406 Textual
407 (text [node]
408 (println "Total Vertexes: " (.getVertexCount node))
409 (println "Total Triangles: " (.getTriangleCount node))
410 (println "Controls :")
411 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
412 (println "Has " (.getQuantity node) " Children:")
413 (doall (map text (.getChildren node)))))
415 (extend-type com.jme3.animation.AnimControl
416 Textual
417 (text [control]
418 (let [animations (.getAnimationNames control)]
419 (println "Animation Control with " (count animations) " animation(s):")
420 (dorun (map println animations)))))
422 (extend-type com.jme3.animation.SkeletonControl
423 Textual
424 (text [control]
425 (println "Skeleton Control with the following skeleton:")
426 (println (.getSkeleton control))))
428 (extend-type com.jme3.bullet.control.KinematicRagdollControl
429 Textual
430 (text [control]
431 (println "Ragdoll Control")))
433 (extend-type com.jme3.scene.Geometry
434 Textual
435 (text [control]
436 (println "...geo...")))
437 #+end_src
439 Here I make the =Viewable= protocol and extend it to JME's types. Now
440 JME3's =hello-world= can be written as easily as:
442 #+begin_src clojure :results silent
443 (cortex.util/view (cortex.util/box))
444 #+end_src
447 * COMMENT code generation
448 #+begin_src clojure :tangle ../src/cortex/import.clj
449 <<import>>
450 #+end_src
453 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
454 <<util>>
455 <<shapes>>
456 <<debug-actions>>
457 <<world-view>>
458 #+end_src