view org/util.org @ 458:42ddfe406c0a

working on depth maps for the lulz.
author Robert McIntyre <rlm@mit.edu>
date Tue, 11 Jun 2013 07:13:42 -0400
parents a44d8a28cbea
children a86555b02916
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-class-names)) "classes")
78 #+end_src
80 #+results:
81 : 938 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)))
136 (defn enable-debug
137 "Turn on debug wireframes for every object in this simulation."
138 [world]
139 (.enableDebug
140 (.getPhysicsSpace
141 (.getState
142 (.getStateManager world)
143 BulletAppState))
144 (asset-manager)))
146 (defn speed-up
147 "Increase the dismally slow speed of the world's camera."
148 [world]
149 (.setMoveSpeed (.getFlyByCamera world)
150 (float 60))
151 (.setRotationSpeed (.getFlyByCamera world)
152 (float 3))
153 world)
156 (defn no-logging
157 "Disable all of jMonkeyEngine's logging."
158 []
159 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
161 (defn set-accuracy
162 "Change the accuracy at which the World's Physics is calculated."
163 [world new-accuracy]
164 (let [physics-manager
165 (.getState
166 (.getStateManager world) BulletAppState)]
167 (.setAccuracy
168 (.getPhysicsSpace physics-manager)
169 (float new-accuracy))))
172 (defn set-gravity
173 "In order to change the gravity of a scene, it is not only necessary
174 to set the gravity variable, but to \"tap\" every physics object in
175 the scene to reactivate physics calculations."
176 [world gravity]
177 (traverse
178 (fn [geom]
179 (if-let
180 ;; only set gravity for physical objects.
181 [control (.getControl geom RigidBodyControl)]
182 (do
183 (.setGravity control gravity)
184 ;; tappsies!
185 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
186 (.getRootNode world)))
188 (defn add-element
189 "Add the Spatial to the world's environment"
190 ([world element node]
191 (.addAll
192 (.getPhysicsSpace
193 (.getState
194 (.getStateManager world)
195 BulletAppState))
196 element)
197 (.attachChild node element))
198 ([world element]
199 (add-element world element (.getRootNode world))))
201 (defn apply-map
202 "Like apply, but works for maps and functions that expect an
203 implicit map and nothing else as in (fn [& {}]).
204 ------- Example -------
205 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
206 (println www))
207 (apply-map demo {:www \"hello!\"})
208 -->\"hello\""
209 [fn m]
210 (apply fn (reduce #(into %1 %2) [] m)))
212 (defn map-vals
213 "Transform a map by applying a function to its values,
214 keeping the keys the same."
215 [f m] (zipmap (keys m) (map f (vals m))))
217 (defn runonce
218 "Decorator. returns a function which will run only once.
219 Inspired by Halloway's version from Lancet."
220 {:author "Robert McIntyre"}
221 [function]
222 (let [sentinel (Object.)
223 result (atom sentinel)]
224 (fn [& args]
225 (locking sentinel
226 (if (= @result sentinel)
227 (reset! result (apply function args))
228 @result)))))
231 #+end_src
233 #+results: util
234 : #'cortex.util/runonce
237 *** Creating Basic Shapes
239 #+name: shapes
240 #+begin_src clojure :results silent
241 (in-ns 'cortex.util)
243 (defn load-bullet
244 "Running this function unpacks the native bullet libraries and makes
245 them available."
246 []
247 (let [sim (world (Node.) {} no-op no-op)]
248 (doto sim
249 (.enqueue
250 (fn []
251 (.stop sim)))
252 (.start))))
255 (defrecord shape-description
256 [name
257 color
258 mass
259 friction
260 texture
261 material
262 position
263 rotation
264 shape
265 physical?
266 GImpact?
267 ])
269 (def base-shape
270 "Basic settings for shapes."
271 (shape-description.
272 "default-shape"
273 false
274 ;;ColorRGBA/Blue
275 1.0 ;; mass
276 1.0 ;; friction
277 ;; texture
278 "Textures/Terrain/BrickWall/BrickWall.jpg"
279 ;; material
280 "Common/MatDefs/Misc/Unshaded.j3md"
281 Vector3f/ZERO
282 Quaternion/IDENTITY
283 (Box. Vector3f/ZERO 0.5 0.5 0.5)
284 true
285 false))
287 (defn make-shape
288 [#^shape-description d]
289 (let [asset-manager (asset-manager)
290 mat (Material. asset-manager (:material d))
291 geom (Geometry. (:name d) (:shape d))]
292 (if (:texture d)
293 (let [key (TextureKey. (:texture d))]
294 (.setGenerateMips key true)
295 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))
296 ))
297 (if (:color d) (.setColor mat "Color" (:color d)))
298 (.setMaterial geom mat)
299 (if-let [rotation (:rotation d)] (.rotate geom rotation))
300 (.setLocalTranslation geom (:position d))
301 (if (:physical? d)
302 (let [physics-control
303 (if (:GImpact d)
304 ;; Create an accurate mesh collision shape if desired.
305 (RigidBodyControl.
306 (doto (GImpactCollisionShape.
307 (.getMesh geom))
308 (.createJmeMesh)
309 ;;(.setMargin 0)
310 )
311 (float (:mass d)))
312 ;; otherwise use jme3's default
313 (RigidBodyControl. (float (:mass d))))]
314 (.addControl geom physics-control)
315 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
316 (.setFriction physics-control (:friction d))))
317 geom))
319 (defn box
320 ([l w h & {:as options}]
321 (let [options (merge base-shape options)]
322 (make-shape (assoc options
323 :shape (Box. l w h)))))
324 ([] (box 0.5 0.5 0.5)))
326 (defn sphere
327 ([r & {:as options}]
328 (let [options (merge base-shape options)]
329 (make-shape (assoc options
330 :shape (Sphere. 32 32 (float r))))))
331 ([] (sphere 0.5)))
333 (defn x-ray
334 "A useful material for debugging -- it can be seen no matter what
335 object occludes it."
336 [#^ColorRGBA color]
337 (doto (Material. (asset-manager)
338 "Common/MatDefs/Misc/Unshaded.j3md")
339 (.setColor "Color" color)
340 (-> (.getAdditionalRenderState)
341 (.setDepthTest false))))
343 (defn node-seq
344 "Take a node and return a seq of all its children
345 recursively. There will be no nodes left in the resulting
346 structure"
347 [#^Node node]
348 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
350 (defn nodify
351 "Take a sequence of things that can be attached to a node and return
352 a node with all of them attached"
353 ([name children]
354 (let [node (Node. name)]
355 (dorun (map #(.attachChild node %) children))
356 node))
357 ([children] (nodify "" children)))
359 (defn load-blender-model
360 "Load a .blend file using an asset folder relative path."
361 [^String model]
362 (.loadModel
363 (doto (asset-manager)
364 (.registerLoader BlenderModelLoader
365 (into-array String ["blend"]))) model))
369 (def brick-length 0.48)
370 (def brick-width 0.24)
371 (def brick-height 0.12)
372 (def gravity (Vector3f. 0 -9.81 0))
374 (import com.jme3.math.Vector2f)
375 (import com.jme3.renderer.queue.RenderQueue$ShadowMode)
376 (import com.jme3.texture.Texture$WrapMode)
378 (defn brick* [position]
379 (println "get brick.")
380 (doto (box brick-length brick-height brick-width
381 :position position :name "brick"
382 :material "Common/MatDefs/Misc/Unshaded.j3md"
383 :texture "Textures/Terrain/BrickWall/BrickWall.jpg"
384 :mass 34)
385 (->
386 (.getMesh)
387 (.scaleTextureCoordinates (Vector2f. 1 0.5)))
388 (.setShadowMode RenderQueue$ShadowMode/CastAndReceive)
389 )
390 )
393 (defn floor*
394 "make a sturdy, unmovable physical floor"
395 []
396 (box 10 0.1 5 :name "floor" :mass 0
397 :color ColorRGBA/Gray :position (Vector3f. 0 0 0)))
399 (defn floor* []
400 (doto (box 10 0.1 5 :name "floor" ;10 0.1 5 ; 240 0.1 240
401 :material "Common/MatDefs/Misc/Unshaded.j3md"
402 :texture "Textures/BronzeCopper030.jpg"
403 :position (Vector3f. 0 0 0 )
404 :mass 0)
405 (->
406 (.getMesh)
407 (.scaleTextureCoordinates (Vector2f. 3 6)));64 64
408 (->
409 (.getMaterial)
410 (.getTextureParam "ColorMap")
411 (.getTextureValue)
412 (.setWrap Texture$WrapMode/Repeat))
413 (.setShadowMode RenderQueue$ShadowMode/Receive)
414 ))
417 (defn brick-wall* []
418 (let [node (Node. "brick-wall")]
419 (dorun
420 (map
421 (comp #(.attachChild node %) brick*)
422 (for [y (range 10)
423 x (range 4)
424 z (range 1)]
425 (Vector3f.
426 (+ (* 2 x brick-length)
427 (if (even? (+ y z))
428 (/ brick-length 4) (/ brick-length -4)))
429 (+ (* brick-height (inc (* 2 y))))
430 (* 2 z brick-width) ))))
431 (.setShadowMode node RenderQueue$ShadowMode/CastAndReceive)
432 node))
435 #+end_src
438 *** Debug Actions
439 #+name: debug-actions
440 #+begin_src clojure :results silent
441 (in-ns 'cortex.util)
443 (defn basic-light-setup
444 "returns a sequence of lights appropriate for fully lighting a scene"
445 []
446 (conj
447 (doall
448 (map
449 (fn [direction]
450 (doto (DirectionalLight.)
451 (.setDirection direction)
452 (.setColor ColorRGBA/White)))
453 [;; six faces of a cube
454 Vector3f/UNIT_X
455 Vector3f/UNIT_Y
456 Vector3f/UNIT_Z
457 (.mult Vector3f/UNIT_X (float -1))
458 (.mult Vector3f/UNIT_Y (float -1))
459 (.mult Vector3f/UNIT_Z (float -1))]))
460 (doto (AmbientLight.)
461 (.setColor ColorRGBA/White))))
463 (defn light-up-everything
464 "Add lights to a world appropriate for quickly seeing everything
465 in the scene. Adds six DirectionalLights facing in orthogonal
466 directions, and one AmbientLight to provide overall lighting
467 coverage."
468 [world]
469 (dorun
470 (map
471 #(.addLight (.getRootNode world) %)
472 (basic-light-setup))))
474 (defn fire-cannon-ball
475 "Creates a function that fires a cannon-ball from the current game's
476 camera. The cannon-ball will be attached to the node if provided, or
477 to the game's RootNode if no node is provided."
478 ([node]
479 (fn [game value]
480 (if (not value)
481 (let [camera (.getCamera game)
482 cannon-ball
483 (sphere 0.4
484 ;;:texture nil
485 :material "Common/MatDefs/Misc/Unshaded.j3md"
486 :color ColorRGBA/Blue
487 :name "cannonball!"
488 :position
489 (.add (.getLocation camera)
490 (.mult (.getDirection camera) (float 1)))
491 :mass 25)] ;200 0.05
492 (.setLinearVelocity
493 (.getControl cannon-ball RigidBodyControl)
494 (.mult (.getDirection camera) (float 50))) ;50
495 (add-element game cannon-ball (if node node (.getRootNode
496 game)))
497 cannon-ball))))
498 ([]
499 (fire-cannon-ball false)))
501 (def standard-debug-controls
502 {"key-space" (fire-cannon-ball)})
505 (defn tap [obj direction force]
506 (let [control (.getControl obj RigidBodyControl)]
507 (.applyTorque
508 control
509 (.mult (.getPhysicsRotation control)
510 (.mult (.normalize direction) (float force))))))
513 (defn with-movement
514 [object
515 [up down left right roll-up roll-down :as keyboard]
516 forces
517 [root-node
518 keymap
519 initialization
520 world-loop]]
521 (let [add-keypress
522 (fn [state keymap key]
523 (merge keymap
524 {key
525 (fn [_ pressed?]
526 (reset! state pressed?))}))
527 move-up? (atom false)
528 move-down? (atom false)
529 move-left? (atom false)
530 move-right? (atom false)
531 roll-left? (atom false)
532 roll-right? (atom false)
534 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
535 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
536 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
537 atoms [move-left? move-right? move-up? move-down?
538 roll-left? roll-right?]
540 keymap* (reduce merge
541 (map #(add-keypress %1 keymap %2)
542 atoms
543 keyboard))
545 splice-loop (fn []
546 (dorun
547 (map
548 (fn [sym direction force]
549 (if @sym
550 (tap object direction force)))
551 atoms directions forces)))
553 world-loop* (fn [world tpf]
554 (world-loop world tpf)
555 (splice-loop))]
556 [root-node
557 keymap*
558 initialization
559 world-loop*]))
561 (import com.jme3.font.BitmapText)
562 (import com.jme3.scene.control.AbstractControl)
563 (import com.aurellem.capture.IsoTimer)
565 (defn display-dilated-time
566 "Shows the time as it is flowing in the simulation on a HUD display.
567 Useful for making videos."
568 [world timer]
569 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
570 text (BitmapText. font false)]
571 (.setLocalTranslation text 300 (.getLineHeight text) 0)
572 (.addControl
573 text
574 (proxy [AbstractControl] []
575 (controlUpdate [tpf]
576 (.setText text (format
577 "%.2f"
578 (float (.getTimeInSeconds timer)))))
579 (controlRender [_ _])))
580 (.attachChild (.getGuiNode world) text)))
581 #+end_src
584 *** Viewing Objects
586 #+name: world-view
587 #+begin_src clojure :results silent
588 (in-ns 'cortex.util)
590 (defprotocol Viewable
591 (view [something]))
593 (extend-type com.jme3.scene.Geometry
594 Viewable
595 (view [geo]
596 (view (doto (Node.)(.attachChild geo)))))
598 (extend-type com.jme3.scene.Node
599 Viewable
600 (view
601 [node]
602 (.start
603 (world
604 node
605 {}
606 (fn [world]
607 (enable-debug world)
608 (set-gravity world Vector3f/ZERO)
609 (light-up-everything world))
610 no-op))))
612 (extend-type com.jme3.math.ColorRGBA
613 Viewable
614 (view
615 [color]
616 (view (doto (Node.)
617 (.attachChild (box 1 1 1 :color color))))))
619 (extend-type ij.ImagePlus
620 Viewable
621 (view [image]
622 (.show image)))
624 (extend-type java.awt.image.BufferedImage
625 Viewable
626 (view
627 [image]
628 (view (ImagePlus. "view-buffered-image" image))))
631 (defprotocol Textual
632 (text [something]
633 "Display a detailed textual analysis of the given object."))
635 (extend-type com.jme3.scene.Node
636 Textual
637 (text [node]
638 (println "Total Vertexes: " (.getVertexCount node))
639 (println "Total Triangles: " (.getTriangleCount node))
640 (println "Controls :")
641 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
642 (println "Has " (.getQuantity node) " Children:")
643 (doall (map text (.getChildren node)))))
645 (extend-type com.jme3.animation.AnimControl
646 Textual
647 (text [control]
648 (let [animations (.getAnimationNames control)]
649 (println "Animation Control with " (count animations) " animation(s):")
650 (dorun (map println animations)))))
652 (extend-type com.jme3.animation.SkeletonControl
653 Textual
654 (text [control]
655 (println "Skeleton Control with the following skeleton:")
656 (println (.getSkeleton control))))
658 (extend-type com.jme3.bullet.control.KinematicRagdollControl
659 Textual
660 (text [control]
661 (println "Ragdoll Control")))
663 (extend-type com.jme3.scene.Geometry
664 Textual
665 (text [control]
666 (println "...geo...")))
668 (extend-type Triangle
669 Textual
670 (text [t]
671 (println "Triangle: " \newline (.get1 t) \newline
672 (.get2 t) \newline (.get3 t))))
674 #+end_src
676 Here I make the =Viewable= protocol and extend it to JME's types. Now
677 JME3's =hello-world= can be written as easily as:
679 #+begin_src clojure :results silent
680 (cortex.util/view (cortex.util/box))
681 #+end_src
684 * COMMENT code generation
685 #+begin_src clojure :tangle ../src/cortex/import.clj
686 <<import>>
687 #+end_src
690 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
691 <<util>>
692 <<shapes>>
693 <<debug-actions>>
694 <<world-view>>
695 #+end_src