view org/util.org @ 371:9c37a55e1cd2

moar literature review.
author Robert McIntyre <rlm@mit.edu>
date Tue, 12 Mar 2013 03:54:30 +0000
parents 9fa92af29c3a
children 4c37d39a3cf6
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 useful for debugging purposes since
56 it allows completion for almost all of JME's classes from the REPL.
58 Out of curiosity, 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 (:import com.jme3.math.Vector3f)
87 (:import com.jme3.math.Quaternion)
88 (:import com.jme3.asset.TextureKey)
89 (:import com.jme3.bullet.control.RigidBodyControl)
90 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
91 (:import com.jme3.scene.shape.Box)
92 (:import com.jme3.scene.Node)
93 (:import com.jme3.scene.shape.Sphere)
94 (:import com.jme3.light.AmbientLight)
95 (:import com.jme3.light.DirectionalLight)
96 (:import (com.jme3.math Triangle ColorRGBA))
97 (:import com.jme3.bullet.BulletAppState)
98 (:import com.jme3.material.Material)
99 (:import com.jme3.scene.Geometry)
100 (:import java.awt.image.BufferedImage)
101 (:import javax.swing.JPanel)
102 (:import javax.swing.JFrame)
103 (:import ij.ImagePlus)
104 (:import javax.swing.SwingUtilities)
105 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)
106 (:import (java.util.logging Level Logger)))
108 (def println-repl
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"
112 (bound-fn [& args] (apply println args)))
114 (defn position-camera
115 "Change the position of the in-world camera."
116 [world #^Vector3f position #^Quaternion rotation]
117 (doto (.getCamera world)
118 (.setLocation position)
119 (.setRotation rotation)))
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 60))
136 (.setRotationSpeed (.getFlyByCamera world)
137 (float 3))
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 (defn map-vals
198 "Transform a map by applying a function to its values,
199 keeping the keys the same."
200 [f m] (zipmap (keys m) (map f (vals m))))
202 (defn runonce
203 "Decorator. returns a function which will run only once.
204 Inspired by Halloway's version from Lancet."
205 {:author "Robert McIntyre"}
206 [function]
207 (let [sentinel (Object.)
208 result (atom sentinel)]
209 (fn [& args]
210 (locking sentinel
211 (if (= @result sentinel)
212 (reset! result (apply function args))
213 @result)))))
216 #+end_src
218 #+results: util
219 : #'cortex.util/runonce
222 *** Creating Basic Shapes
224 #+name: shapes
225 #+begin_src clojure :results silent
226 (in-ns 'cortex.util)
228 (defn load-bullet
229 "Running this function unpacks the native bullet libraries and makes
230 them available."
231 []
232 (let [sim (world (Node.) {} no-op no-op)]
233 (doto sim
234 (.enqueue
235 (fn []
236 (.stop sim)))
237 (.start))))
240 (defrecord shape-description
241 [name
242 color
243 mass
244 friction
245 texture
246 material
247 position
248 rotation
249 shape
250 physical?
251 GImpact?
252 ])
254 (def base-shape
255 "Basic settings for shapes."
256 (shape-description.
257 "default-shape"
258 false
259 ;;ColorRGBA/Blue
260 1.0 ;; mass
261 1.0 ;; friction
262 ;; texture
263 "Textures/Terrain/BrickWall/BrickWall.jpg"
264 ;; material
265 "Common/MatDefs/Misc/Unshaded.j3md"
266 Vector3f/ZERO
267 Quaternion/IDENTITY
268 (Box. Vector3f/ZERO 0.5 0.5 0.5)
269 true
270 false))
272 (defn make-shape
273 [#^shape-description d]
274 (let [asset-manager (asset-manager)
275 mat (Material. asset-manager (:material d))
276 geom (Geometry. (:name d) (:shape d))]
277 (if (:texture d)
278 (let [key (TextureKey. (:texture d))]
279 (.setGenerateMips key true)
280 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))
281 ))
282 (if (:color d) (.setColor mat "Color" (:color d)))
283 (.setMaterial geom mat)
284 (if-let [rotation (:rotation d)] (.rotate geom rotation))
285 (.setLocalTranslation geom (:position d))
286 (if (:physical? d)
287 (let [physics-control
288 (if (:GImpact d)
289 ;; Create an accurate mesh collision shape if desired.
290 (RigidBodyControl.
291 (doto (GImpactCollisionShape.
292 (.getMesh geom))
293 (.createJmeMesh)
294 ;;(.setMargin 0)
295 )
296 (float (:mass d)))
297 ;; otherwise use jme3's default
298 (RigidBodyControl. (float (:mass d))))]
299 (.addControl geom physics-control)
300 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
301 (.setFriction physics-control (:friction d))))
302 geom))
304 (defn box
305 ([l w h & {:as options}]
306 (let [options (merge base-shape options)]
307 (make-shape (assoc options
308 :shape (Box. l w h)))))
309 ([] (box 0.5 0.5 0.5)))
311 (defn sphere
312 ([r & {:as options}]
313 (let [options (merge base-shape options)]
314 (make-shape (assoc options
315 :shape (Sphere. 32 32 (float r))))))
316 ([] (sphere 0.5)))
318 (defn x-ray
319 "A useful material for debugging -- it can be seen no matter what
320 object occludes it."
321 [#^ColorRGBA color]
322 (doto (Material. (asset-manager)
323 "Common/MatDefs/Misc/Unshaded.j3md")
324 (.setColor "Color" color)
325 (-> (.getAdditionalRenderState)
326 (.setDepthTest false))))
328 (defn node-seq
329 "Take a node and return a seq of all its children
330 recursively. There will be no nodes left in the resulting
331 structure"
332 [#^Node node]
333 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
335 (defn nodify
336 "Take a sequence of things that can be attached to a node and return
337 a node with all of them attached"
338 ([name children]
339 (let [node (Node. name)]
340 (dorun (map #(.attachChild node %) children))
341 node))
342 ([children] (nodify "" children)))
344 (defn load-blender-model
345 "Load a .blend file using an asset folder relative path."
346 [^String model]
347 (.loadModel
348 (doto (asset-manager)
349 (.registerLoader BlenderModelLoader
350 (into-array String ["blend"]))) model))
353 #+end_src
356 *** Debug Actions
357 #+name: debug-actions
358 #+begin_src clojure :results silent
359 (in-ns 'cortex.util)
361 (defn basic-light-setup
362 "returns a sequence of lights appropriate for fully lighting a scene"
363 []
364 (conj
365 (doall
366 (map
367 (fn [direction]
368 (doto (DirectionalLight.)
369 (.setDirection direction)
370 (.setColor ColorRGBA/White)))
371 [;; six faces of a cube
372 Vector3f/UNIT_X
373 Vector3f/UNIT_Y
374 Vector3f/UNIT_Z
375 (.mult Vector3f/UNIT_X (float -1))
376 (.mult Vector3f/UNIT_Y (float -1))
377 (.mult Vector3f/UNIT_Z (float -1))]))
378 (doto (AmbientLight.)
379 (.setColor ColorRGBA/White))))
381 (defn light-up-everything
382 "Add lights to a world appropriate for quickly seeing everything
383 in the scene. Adds six DirectionalLights facing in orthogonal
384 directions, and one AmbientLight to provide overall lighting
385 coverage."
386 [world]
387 (dorun
388 (map
389 #(.addLight (.getRootNode world) %)
390 (basic-light-setup))))
392 (defn fire-cannon-ball
393 "Creates a function that fires a cannon-ball from the current game's
394 camera. The cannon-ball will be attached to the node if provided, or
395 to the game's RootNode if no node is provided."
396 ([node]
397 (fn [game value]
398 (if (not value)
399 (let [camera (.getCamera game)
400 cannon-ball
401 (sphere 0.4
402 ;;:texture nil
403 :material "Common/MatDefs/Misc/Unshaded.j3md"
404 :color ColorRGBA/Blue
405 :name "cannonball!"
406 :position
407 (.add (.getLocation camera)
408 (.mult (.getDirection camera) (float 1)))
409 :mass 25)] ;200 0.05
410 (.setLinearVelocity
411 (.getControl cannon-ball RigidBodyControl)
412 (.mult (.getDirection camera) (float 50))) ;50
413 (add-element game cannon-ball (if node node (.getRootNode
414 game)))
415 cannon-ball))))
416 ([]
417 (fire-cannon-ball false)))
419 (def standard-debug-controls
420 {"key-space" (fire-cannon-ball)})
423 (defn tap [obj direction force]
424 (let [control (.getControl obj RigidBodyControl)]
425 (.applyTorque
426 control
427 (.mult (.getPhysicsRotation control)
428 (.mult (.normalize direction) (float force))))))
431 (defn with-movement
432 [object
433 [up down left right roll-up roll-down :as keyboard]
434 forces
435 [root-node
436 keymap
437 initialization
438 world-loop]]
439 (let [add-keypress
440 (fn [state keymap key]
441 (merge keymap
442 {key
443 (fn [_ pressed?]
444 (reset! state pressed?))}))
445 move-up? (atom false)
446 move-down? (atom false)
447 move-left? (atom false)
448 move-right? (atom false)
449 roll-left? (atom false)
450 roll-right? (atom false)
452 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
453 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
454 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
455 atoms [move-left? move-right? move-up? move-down?
456 roll-left? roll-right?]
458 keymap* (reduce merge
459 (map #(add-keypress %1 keymap %2)
460 atoms
461 keyboard))
463 splice-loop (fn []
464 (dorun
465 (map
466 (fn [sym direction force]
467 (if @sym
468 (tap object direction force)))
469 atoms directions forces)))
471 world-loop* (fn [world tpf]
472 (world-loop world tpf)
473 (splice-loop))]
474 [root-node
475 keymap*
476 initialization
477 world-loop*]))
479 (import com.jme3.font.BitmapText)
480 (import com.jme3.scene.control.AbstractControl)
481 (import com.aurellem.capture.IsoTimer)
483 (defn display-dilated-time
484 "Shows the time as it is flowing in the simulation on a HUD display.
485 Useful for making videos."
486 [world timer]
487 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
488 text (BitmapText. font false)]
489 (.setLocalTranslation text 300 (.getLineHeight text) 0)
490 (.addControl
491 text
492 (proxy [AbstractControl] []
493 (controlUpdate [tpf]
494 (.setText text (format
495 "%.2f"
496 (float (.getTimeInSeconds timer)))))
497 (controlRender [_ _])))
498 (.attachChild (.getGuiNode world) text)))
499 #+end_src
502 *** Viewing Objects
504 #+name: world-view
505 #+begin_src clojure :results silent
506 (in-ns 'cortex.util)
508 (defprotocol Viewable
509 (view [something]))
511 (extend-type com.jme3.scene.Geometry
512 Viewable
513 (view [geo]
514 (view (doto (Node.)(.attachChild geo)))))
516 (extend-type com.jme3.scene.Node
517 Viewable
518 (view
519 [node]
520 (.start
521 (world
522 node
523 {}
524 (fn [world]
525 (enable-debug world)
526 (set-gravity world Vector3f/ZERO)
527 (light-up-everything world))
528 no-op))))
530 (extend-type com.jme3.math.ColorRGBA
531 Viewable
532 (view
533 [color]
534 (view (doto (Node.)
535 (.attachChild (box 1 1 1 :color color))))))
537 (extend-type ij.ImagePlus
538 Viewable
539 (view [image]
540 (.show image)))
542 (extend-type java.awt.image.BufferedImage
543 Viewable
544 (view
545 [image]
546 (view (ImagePlus. "view-buffered-image" image))))
549 (defprotocol Textual
550 (text [something]
551 "Display a detailed textual analysis of the given object."))
553 (extend-type com.jme3.scene.Node
554 Textual
555 (text [node]
556 (println "Total Vertexes: " (.getVertexCount node))
557 (println "Total Triangles: " (.getTriangleCount node))
558 (println "Controls :")
559 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
560 (println "Has " (.getQuantity node) " Children:")
561 (doall (map text (.getChildren node)))))
563 (extend-type com.jme3.animation.AnimControl
564 Textual
565 (text [control]
566 (let [animations (.getAnimationNames control)]
567 (println "Animation Control with " (count animations) " animation(s):")
568 (dorun (map println animations)))))
570 (extend-type com.jme3.animation.SkeletonControl
571 Textual
572 (text [control]
573 (println "Skeleton Control with the following skeleton:")
574 (println (.getSkeleton control))))
576 (extend-type com.jme3.bullet.control.KinematicRagdollControl
577 Textual
578 (text [control]
579 (println "Ragdoll Control")))
581 (extend-type com.jme3.scene.Geometry
582 Textual
583 (text [control]
584 (println "...geo...")))
586 (extend-type Triangle
587 Textual
588 (text [t]
589 (println "Triangle: " \newline (.get1 t) \newline
590 (.get2 t) \newline (.get3 t))))
592 #+end_src
594 Here I make the =Viewable= protocol and extend it to JME's types. Now
595 JME3's =hello-world= can be written as easily as:
597 #+begin_src clojure :results silent
598 (cortex.util/view (cortex.util/box))
599 #+end_src
602 * COMMENT code generation
603 #+begin_src clojure :tangle ../src/cortex/import.clj
604 <<import>>
605 #+end_src
608 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
609 <<util>>
610 <<shapes>>
611 <<debug-actions>>
612 <<world-view>>
613 #+end_src