view org/util.org @ 205:d3a2abfac405

fixed tangling in body.org, added dialated time HUD display in cortex.util
author Robert McIntyre <rlm@mit.edu>
date Thu, 09 Feb 2012 04:15:09 -0700
parents 305439cec54d
children f5ea63245b3b
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 Triangle ColorRGBA))
98 (:import com.jme3.bullet.BulletAppState)
99 (:import com.jme3.material.Material)
100 (:import com.jme3.scene.Geometry)
101 (:import java.awt.image.BufferedImage)
102 (:import javax.swing.JPanel)
103 (:import javax.swing.JFrame)
104 (:import javax.swing.SwingUtilities)
105 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)
106 (:import (java.util.logging Level Logger)))
108 (defvar println-repl
109 (bound-fn [& args] (apply println args))
110 "println called from the LWJGL thread will not go to the REPL, but
111 instead to whatever terminal started the JVM process. This function
112 will always output to the REPL")
114 (defn position-camera
115 "Change the position of the in-world camera."
116 ([world position direction up]
117 (doto (.getCamera world)
118 (.setLocation )
119 (.lookAt direction up)))
120 ([world position direction]
121 (position-camera
122 world position direction Vector3f/UNIT_Y)))
124 (defn enable-debug
125 "Turn on debug wireframes for every object in this simulation."
126 [world]
127 (.enableDebug
128 (.getPhysicsSpace
129 (.getState
130 (.getStateManager world)
131 BulletAppState))
132 (asset-manager)))
134 (defn speed-up
135 "Increase the dismally slow speed of the world's camera."
136 [world]
137 (.setMoveSpeed (.getFlyByCamera world)
138 (float 60))
139 (.setRotationSpeed (.getFlyByCamera world)
140 (float 3))
141 world)
144 (defn no-logging
145 "Disable all of jMonkeyEngine's logging."
146 []
147 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
149 (defn set-accuracy
150 "Change the accuracy at which the World's Physics is calculated."
151 [world new-accuracy]
152 (let [physics-manager
153 (.getState
154 (.getStateManager world) BulletAppState)]
155 (.setAccuracy
156 (.getPhysicsSpace physics-manager)
157 (float new-accuracy))))
160 (defn set-gravity
161 "In order to change the gravity of a scene, it is not only necessary
162 to set the gravity variable, but to \"tap\" every physics object in
163 the scene to reactivate physics calculations."
164 [world gravity]
165 (traverse
166 (fn [geom]
167 (if-let
168 ;; only set gravity for physical objects.
169 [control (.getControl geom RigidBodyControl)]
170 (do
171 (.setGravity control gravity)
172 ;; tappsies!
173 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
174 (.getRootNode world)))
176 (defn add-element
177 "Add the Spatial to the world's environment"
178 ([world element node]
179 (.addAll
180 (.getPhysicsSpace
181 (.getState
182 (.getStateManager world)
183 BulletAppState))
184 element)
185 (.attachChild node element))
186 ([world element]
187 (add-element world element (.getRootNode world))))
189 (defn apply-map
190 "Like apply, but works for maps and functions that expect an
191 implicit map and nothing else as in (fn [& {}]).
192 ------- Example -------
193 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
194 (println www))
195 (apply-map demo {:www \"hello!\"})
196 -->\"hello\""
197 [fn m]
198 (apply fn (reduce #(into %1 %2) [] m)))
200 (defn map-vals
201 "Transform a map by applying a function to its values,
202 keeping the keys the same."
203 [f m] (zipmap (keys m) (map f (vals m))))
205 (defn runonce
206 "Decorator. returns a function which will run only once.
207 Inspired by Halloway's version from Lancet."
208 {:author "Robert McIntyre"}
209 [function]
210 (let [sentinel (Object.)
211 result (atom sentinel)]
212 (fn [& args]
213 (locking sentinel
214 (if (= @result sentinel)
215 (reset! result (apply function args))
216 @result)))))
219 #+end_src
221 #+results: util
222 : #'cortex.util/apply-map
225 *** Creating Basic Shapes
227 #+name: shapes
228 #+begin_src clojure :results silent
229 (in-ns 'cortex.util)
231 (defn load-bullet
232 "Runnig this function unpacks the native bullet libraries and makes
233 them available."
234 []
235 (let [sim (world (Node.) {} no-op no-op)]
236 (doto sim
237 (.enqueue
238 (fn []
239 (.stop sim)))
240 (.start))))
243 (defrecord shape-description
244 [name
245 color
246 mass
247 friction
248 texture
249 material
250 position
251 rotation
252 shape
253 physical?
254 GImpact?
255 ])
257 (defvar base-shape
258 (shape-description.
259 "default-shape"
260 false
261 ;;ColorRGBA/Blue
262 1.0 ;; mass
263 1.0 ;; friction
264 ;; texture
265 "Textures/Terrain/BrickWall/BrickWall.jpg"
266 ;; material
267 "Common/MatDefs/Misc/Unshaded.j3md"
268 Vector3f/ZERO
269 Quaternion/IDENTITY
270 (Box. Vector3f/ZERO 0.5 0.5 0.5)
271 true
272 false)
273 "Basic settings for shapes.")
275 (defn make-shape
276 [#^shape-description d]
277 (let [asset-manager (asset-manager)
278 mat (Material. asset-manager (:material d))
279 geom (Geometry. (:name d) (:shape d))]
280 (if (:texture d)
281 (let [key (TextureKey. (:texture d))]
282 ;;(.setGenerateMips key true)
283 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
284 ))
285 (if (:color d) (.setColor mat "Color" (:color d)))
286 (.setMaterial geom mat)
287 (if-let [rotation (:rotation d)] (.rotate geom rotation))
288 (.setLocalTranslation geom (:position d))
289 (if (:physical? d)
290 (let [physics-control
291 (if (:GImpact d)
292 ;; Create an accurate mesh collision shape if desired.
293 (RigidBodyControl.
294 (doto (GImpactCollisionShape.
295 (.getMesh geom))
296 (.createJmeMesh)
297 ;;(.setMargin 0)
298 )
299 (float (:mass d)))
300 ;; otherwise use jme3's default
301 (RigidBodyControl. (float (:mass d))))]
302 (.addControl geom physics-control)
303 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
304 (.setFriction physics-control (:friction d))))
305 geom))
307 (defn box
308 ([l w h & {:as options}]
309 (let [options (merge base-shape options)]
310 (make-shape (assoc options
311 :shape (Box. l w h)))))
312 ([] (box 0.5 0.5 0.5)))
314 (defn sphere
315 ([r & {:as options}]
316 (let [options (merge base-shape options)]
317 (make-shape (assoc options
318 :shape (Sphere. 32 32 (float r))))))
319 ([] (sphere 0.5)))
321 (defn x-ray
322 "A usefull material for debuging -- it can be seen no matter what
323 object occuldes it."
324 [#^ColorRGBA color]
325 (doto (Material. (asset-manager)
326 "Common/MatDefs/Misc/Unshaded.j3md")
327 (.setColor "Color" color)
328 (-> (.getAdditionalRenderState)
329 (.setDepthTest false))))
331 (defn node-seq
332 "Take a node and return a seq of all its children
333 recursively. There will be no nodes left in the resulting
334 structure"
335 [#^Node node]
336 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
338 (defn nodify
339 "Take a sequence of things that can be attached to a node and return
340 a node with all of them attached"
341 ([name children]
342 (let [node (Node. name)]
343 (dorun (map #(.attachChild node %) children))
344 node))
345 ([children] (nodify "" children)))
347 (defn load-blender-model
348 "Load a .blend file using an asset folder relative path."
349 [^String model]
350 (.loadModel
351 (doto (asset-manager)
352 (.registerLoader BlenderModelLoader
353 (into-array String ["blend"]))) model))
356 #+end_src
359 *** Debug Actions
360 #+name: debug-actions
361 #+begin_src clojure :results silent
362 (in-ns 'cortex.util)
364 (defn basic-light-setup
365 "returns a sequence of lights appropiate for fully lighting a scene"
366 []
367 (conj
368 (doall
369 (map
370 (fn [direction]
371 (doto (DirectionalLight.)
372 (.setDirection direction)
373 (.setColor ColorRGBA/White)))
374 [;; six faces of a cube
375 Vector3f/UNIT_X
376 Vector3f/UNIT_Y
377 Vector3f/UNIT_Z
378 (.mult Vector3f/UNIT_X (float -1))
379 (.mult Vector3f/UNIT_Y (float -1))
380 (.mult Vector3f/UNIT_Z (float -1))]))
381 (doto (AmbientLight.)
382 (.setColor ColorRGBA/White))))
384 (defn light-up-everything
385 "Add lights to a world appropiate for quickly seeing everything
386 in the scene. Adds six DirectionalLights facing in orthogonal
387 directions, and one AmbientLight to provide overall lighting
388 coverage."
389 [world]
390 (dorun
391 (map
392 #(.addLight (.getRootNode world) %)
393 (basic-light-setup))))
395 (defn fire-cannon-ball
396 "Creates a function that fires a cannon-ball from the current game's
397 camera. The cannon-ball will be attached to the node if provided, or
398 to the game's RootNode if no node is provided."
399 ([node]
400 (fn [game value]
401 (if (not value)
402 (let [camera (.getCamera game)
403 cannon-ball
404 (sphere 0.7
405 :material "Common/MatDefs/Misc/Unshaded.j3md"
406 :texture "Textures/PokeCopper.jpg"
407 :position
408 (.add (.getLocation camera)
409 (.mult (.getDirection camera) (float 1)))
410 :mass 3)] ;200 0.05
411 (.setLinearVelocity
412 (.getControl cannon-ball RigidBodyControl)
413 (.mult (.getDirection camera) (float 50))) ;50
414 (add-element game cannon-ball (if node node (.getRootNode game)))))))
415 ([]
416 (fire-cannon-ball false)))
418 (def standard-debug-controls
419 {"key-space" (fire-cannon-ball)})
422 (defn tap [obj direction force]
423 (let [control (.getControl obj RigidBodyControl)]
424 (.applyTorque
425 control
426 (.mult (.getPhysicsRotation control)
427 (.mult (.normalize direction) (float force))))))
430 (defn with-movement
431 [object
432 [up down left right roll-up roll-down :as keyboard]
433 forces
434 [root-node
435 keymap
436 intilization
437 world-loop]]
438 (let [add-keypress
439 (fn [state keymap key]
440 (merge keymap
441 {key
442 (fn [_ pressed?]
443 (reset! state pressed?))}))
444 move-up? (atom false)
445 move-down? (atom false)
446 move-left? (atom false)
447 move-right? (atom false)
448 roll-left? (atom false)
449 roll-right? (atom false)
451 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
452 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
453 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
454 atoms [move-left? move-right? move-up? move-down?
455 roll-left? roll-right?]
457 keymap* (reduce merge
458 (map #(add-keypress %1 keymap %2)
459 atoms
460 keyboard))
462 splice-loop (fn []
463 (dorun
464 (map
465 (fn [sym direction force]
466 (if @sym
467 (tap object direction force)))
468 atoms directions forces)))
470 world-loop* (fn [world tpf]
471 (world-loop world tpf)
472 (splice-loop))]
473 [root-node
474 keymap*
475 intilization
476 world-loop*]))
478 (import com.jme3.font.BitmapText)
479 (import com.jme3.scene.control.AbstractControl)
480 (import com.aurellem.capture.IsoTimer)
482 (defn display-dialated-time
483 "Shows the time as it is flowing in the simulation on a HUD display.
484 Useful for making videos."
485 [world timer]
486 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
487 text (BitmapText. font false)]
488 (.setLocalTranslation text 300 (.getLineHeight text) 0)
489 (.addControl
490 text
491 (proxy [AbstractControl] []
492 (controlUpdate [tpf]
493 (.setText text (format
494 "%.2f"
495 (float (/ (.getTime timer) 1000)))))
496 (controlRender [_ _])))
497 (.attachChild (.getGuiNode world) text)))
498 #+end_src
501 *** Viewing Objects
503 #+name: world-view
504 #+begin_src clojure :results silent
505 (in-ns 'cortex.util)
507 (defprotocol Viewable
508 (view [something]))
510 (extend-type com.jme3.scene.Geometry
511 Viewable
512 (view [geo]
513 (view (doto (Node.)(.attachChild geo)))))
515 (extend-type com.jme3.scene.Node
516 Viewable
517 (view
518 [node]
519 (.start
520 (world
521 node
522 {}
523 (fn [world]
524 (enable-debug world)
525 (set-gravity world Vector3f/ZERO)
526 (light-up-everything world))
527 no-op))))
529 (extend-type com.jme3.math.ColorRGBA
530 Viewable
531 (view
532 [color]
533 (view (doto (Node.)
534 (.attachChild (box 1 1 1 :color color))))))
536 (defprotocol Textual
537 (text [something]
538 "Display a detailed textual analysis of the given object."))
540 (extend-type com.jme3.scene.Node
541 Textual
542 (text [node]
543 (println "Total Vertexes: " (.getVertexCount node))
544 (println "Total Triangles: " (.getTriangleCount node))
545 (println "Controls :")
546 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
547 (println "Has " (.getQuantity node) " Children:")
548 (doall (map text (.getChildren node)))))
550 (extend-type com.jme3.animation.AnimControl
551 Textual
552 (text [control]
553 (let [animations (.getAnimationNames control)]
554 (println "Animation Control with " (count animations) " animation(s):")
555 (dorun (map println animations)))))
557 (extend-type com.jme3.animation.SkeletonControl
558 Textual
559 (text [control]
560 (println "Skeleton Control with the following skeleton:")
561 (println (.getSkeleton control))))
563 (extend-type com.jme3.bullet.control.KinematicRagdollControl
564 Textual
565 (text [control]
566 (println "Ragdoll Control")))
568 (extend-type com.jme3.scene.Geometry
569 Textual
570 (text [control]
571 (println "...geo...")))
573 (extend-type Triangle
574 Textual
575 (text [t]
576 (println "Triangle: " \newline (.get1 t) \newline
577 (.get2 t) \newline (.get3 t))))
579 #+end_src
581 Here I make the =Viewable= protocol and extend it to JME's types. Now
582 JME3's =hello-world= can be written as easily as:
584 #+begin_src clojure :results silent
585 (cortex.util/view (cortex.util/box))
586 #+end_src
589 * COMMENT code generation
590 #+begin_src clojure :tangle ../src/cortex/import.clj
591 <<import>>
592 #+end_src
595 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
596 <<util>>
597 <<shapes>>
598 <<debug-actions>>
599 <<world-view>>
600 #+end_src