view org/util.org @ 400:6ba908c1a0a9

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