view org/util.org @ 473:486ce07f5545

s.
author Robert McIntyre <rlm@mit.edu>
date Fri, 28 Mar 2014 20:49:13 -0400
parents 763d13f77e03
children 01934317b25b
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 (import com.aurellem.capture.IsoTimer)
63 (map (comp permissive-import symbol) (jme-class-names))))
64 #+end_src
66 jMonkeyEngine3 has a plethora of classes which can be overwhelming to
67 manage. This code uses reflection to import all of them. Once I'm
68 happy with the general structure of a namespace I can deal with
69 importing only the classes it actually needs.
71 The =mega-import-jme3= is quite useful for debugging purposes since
72 it allows completion for almost all of JME's classes from the REPL.
74 Out of curiosity, let's see just how many classes =mega-import-jme3=
75 imports:
77 #+begin_src clojure :exports both :results output
78 (println (clojure.core/count (cortex.import/jme-class-names)) "classes")
79 #+end_src
81 #+results:
82 : 938 classes
85 * Utilities
87 The utilities here come in three main groups:
88 - Changing settings in a running =Application=
89 - Creating objects
90 - Debug Actions
91 - Visualizing objects
93 *** Changing Settings
95 #+name: util
96 #+begin_src clojure
97 (ns cortex.util
98 "Utility functions for making jMonkeyEngine3 easier to program from
99 clojure."
100 {:author "Robert McIntyre"}
101 (:use cortex.world)
102 (:import com.jme3.math.Vector3f)
103 (:import com.jme3.math.Quaternion)
104 (:import com.jme3.asset.TextureKey)
105 (:import com.jme3.bullet.control.RigidBodyControl)
106 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
107 (:import com.jme3.scene.shape.Box)
108 (:import com.jme3.scene.Node)
109 (:import com.jme3.scene.shape.Sphere)
110 (:import com.jme3.light.AmbientLight)
111 (:import com.jme3.light.DirectionalLight)
112 (:import (com.jme3.math Triangle ColorRGBA))
113 (:import com.jme3.bullet.BulletAppState)
114 (:import com.jme3.material.Material)
115 (:import com.jme3.scene.Geometry)
116 (:import java.awt.image.BufferedImage)
117 (:import javax.swing.JPanel)
118 (:import javax.swing.JFrame)
119 (:import ij.ImagePlus)
120 (:import javax.swing.SwingUtilities)
121 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)
122 (:import (java.util.logging Level Logger)))
124 (def println-repl
125 "println called from the LWJGL thread will not go to the REPL, but
126 instead to whatever terminal started the JVM process. This function
127 will always output to the REPL"
128 (bound-fn [& args] (apply println args)))
130 (defn position-camera
131 "Change the position of the in-world camera."
132 ([world #^Vector3f position #^Quaternion rotation]
133 (doto (.getCamera world)
134 (.setLocation position)
135 (.setRotation rotation)))
136 ([world [position rotation]]
137 (position-camera world position rotation)))
140 (defn enable-debug
141 "Turn on debug wireframes for every object in this simulation."
142 [world]
143 (.enableDebug
144 (.getPhysicsSpace
145 (.getState
146 (.getStateManager world)
147 BulletAppState))
148 (asset-manager)))
150 (defn speed-up
151 "Increase the dismally slow speed of the world's camera."
152 ([world] (speed-up world 1))
153 ([world amount]
154 (.setMoveSpeed (.getFlyByCamera world)
155 (float (* amount 60)))
156 (.setRotationSpeed (.getFlyByCamera world)
157 (float (* amount 3)))
158 world))
160 (defn no-logging
161 "Disable all of jMonkeyEngine's logging."
162 []
163 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
165 (defn set-accuracy
166 "Change the accuracy at which the World's Physics is calculated."
167 [world new-accuracy]
168 (let [physics-manager
169 (.getState
170 (.getStateManager world) BulletAppState)]
171 (.setAccuracy
172 (.getPhysicsSpace physics-manager)
173 (float new-accuracy))))
176 (defn set-gravity
177 "In order to change the gravity of a scene, it is not only necessary
178 to set the gravity variable, but to \"tap\" every physics object in
179 the scene to reactivate physics calculations."
180 [world gravity]
181 (traverse
182 (fn [geom]
183 (if-let
184 ;; only set gravity for physical objects.
185 [control (.getControl geom RigidBodyControl)]
186 (do
187 (.setGravity control gravity)
188 ;; tappsies!
189 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
190 (.getRootNode world)))
192 (defn add-element
193 "Add the Spatial to the world's environment"
194 ([world element node]
195 (.addAll
196 (.getPhysicsSpace
197 (.getState
198 (.getStateManager world)
199 BulletAppState))
200 element)
201 (.attachChild node element))
202 ([world element]
203 (add-element world element (.getRootNode world))))
205 (defn apply-map
206 "Like apply, but works for maps and functions that expect an
207 implicit map and nothing else as in (fn [& {}]).
208 ------- Example -------
209 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
210 (println www))
211 (apply-map demo {:www \"hello!\"})
212 -->\"hello\""
213 [fn m]
214 (apply fn (reduce #(into %1 %2) [] m)))
216 (defn map-vals
217 "Transform a map by applying a function to its values,
218 keeping the keys the same."
219 [f m] (zipmap (keys m) (map f (vals m))))
221 (defn runonce
222 "Decorator. returns a function which will run only once.
223 Inspired by Halloway's version from Lancet."
224 {:author "Robert McIntyre"}
225 [function]
226 (let [sentinel (Object.)
227 result (atom sentinel)]
228 (fn [& args]
229 (locking sentinel
230 (if (= @result sentinel)
231 (reset! result (apply function args))
232 @result)))))
235 #+end_src
237 #+results: util
238 : #'cortex.util/runonce
241 *** Creating Basic Shapes
243 #+name: shapes
244 #+begin_src clojure :results silent
245 (in-ns 'cortex.util)
247 (defn load-bullet
248 "Running this function unpacks the native bullet libraries and makes
249 them available."
250 []
251 (let [sim (world (Node.) {} no-op no-op)]
252 (doto sim
253 (.enqueue
254 (fn []
255 (.stop sim)))
256 (.start))))
259 (defrecord shape-description
260 [name
261 color
262 mass
263 friction
264 texture
265 material
266 position
267 rotation
268 shape
269 physical?
270 GImpact?
271 ])
273 (def base-shape
274 "Basic settings for shapes."
275 (shape-description.
276 "default-shape"
277 false
278 ;;ColorRGBA/Blue
279 1.0 ;; mass
280 1.0 ;; friction
281 ;; texture
282 "Textures/Terrain/BrickWall/BrickWall.jpg"
283 ;; material
284 "Common/MatDefs/Misc/Unshaded.j3md"
285 Vector3f/ZERO
286 Quaternion/IDENTITY
287 (Box. Vector3f/ZERO 0.5 0.5 0.5)
288 true
289 false))
291 (defn make-shape
292 [#^shape-description d]
293 (let [asset-manager (asset-manager)
294 mat (Material. asset-manager (:material d))
295 geom (Geometry. (:name d) (:shape d))]
296 (if (:texture d)
297 (let [key (TextureKey. (:texture d))]
298 (.setGenerateMips key true)
299 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))
300 ))
301 (if (:color d) (.setColor mat "Color" (:color d)))
302 (.setMaterial geom mat)
303 (if-let [rotation (:rotation d)] (.rotate geom rotation))
304 (.setLocalTranslation geom (:position d))
305 (if (:physical? d)
306 (let [physics-control
307 (if (:GImpact d)
308 ;; Create an accurate mesh collision shape if desired.
309 (RigidBodyControl.
310 (doto (GImpactCollisionShape.
311 (.getMesh geom))
312 (.createJmeMesh)
313 ;;(.setMargin 0)
314 )
315 (float (:mass d)))
316 ;; otherwise use jme3's default
317 (RigidBodyControl. (float (:mass d))))]
318 (.addControl geom physics-control)
319 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
320 (.setFriction physics-control (:friction d))))
321 geom))
323 (defn box
324 ([l w h & {:as options}]
325 (let [options (merge base-shape options)]
326 (make-shape (assoc options
327 :shape (Box. l w h)))))
328 ([] (box 0.5 0.5 0.5)))
330 (defn sphere
331 ([r & {:as options}]
332 (let [options (merge base-shape options)]
333 (make-shape (assoc options
334 :shape (Sphere. 32 32 (float r))))))
335 ([] (sphere 0.5)))
337 (defn x-ray
338 "A useful material for debugging -- it can be seen no matter what
339 object occludes it."
340 [#^ColorRGBA color]
341 (doto (Material. (asset-manager)
342 "Common/MatDefs/Misc/Unshaded.j3md")
343 (.setColor "Color" color)
344 (-> (.getAdditionalRenderState)
345 (.setDepthTest false))))
347 (defn node-seq
348 "Take a node and return a seq of all its children
349 recursively. There will be no nodes left in the resulting
350 structure"
351 [#^Node node]
352 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
354 (defn nodify
355 "Take a sequence of things that can be attached to a node and return
356 a node with all of them attached"
357 ([name children]
358 (let [node (Node. name)]
359 (dorun (map #(.attachChild node %) children))
360 node))
361 ([children] (nodify "" children)))
363 (defn load-blender-model
364 "Load a .blend file using an asset folder relative path."
365 [^String model]
366 (.loadModel
367 (doto (asset-manager)
368 (.registerLoader BlenderModelLoader
369 (into-array String ["blend"]))) model))
373 (def brick-length 0.48)
374 (def brick-width 0.24)
375 (def brick-height 0.12)
376 (def gravity (Vector3f. 0 -9.81 0))
378 (import com.jme3.math.Vector2f)
379 (import com.jme3.renderer.queue.RenderQueue$ShadowMode)
380 (import com.jme3.texture.Texture$WrapMode)
382 (defn brick* [position]
383 (println "get brick.")
384 (doto (box brick-length brick-height brick-width
385 :position position :name "brick"
386 :material "Common/MatDefs/Misc/Unshaded.j3md"
387 :texture "Textures/Terrain/BrickWall/BrickWall.jpg"
388 :mass 34)
389 (->
390 (.getMesh)
391 (.scaleTextureCoordinates (Vector2f. 1 0.5)))
392 (.setShadowMode RenderQueue$ShadowMode/CastAndReceive)
393 )
394 )
397 (defn floor*
398 "make a sturdy, unmovable physical floor"
399 []
400 (box 10 0.1 5 :name "floor" :mass 0
401 :color ColorRGBA/Gray :position (Vector3f. 0 0 0)))
403 (defn floor* []
404 (doto (box 10 0.1 5 :name "floor" ;10 0.1 5 ; 240 0.1 240
405 :material "Common/MatDefs/Misc/Unshaded.j3md"
406 :texture "Textures/BronzeCopper030.jpg"
407 :position (Vector3f. 0 0 0 )
408 :mass 0)
409 (->
410 (.getMesh)
411 (.scaleTextureCoordinates (Vector2f. 3 6)));64 64
412 (->
413 (.getMaterial)
414 (.getTextureParam "ColorMap")
415 (.getTextureValue)
416 (.setWrap Texture$WrapMode/Repeat))
417 (.setShadowMode RenderQueue$ShadowMode/Receive)
418 ))
421 (defn brick-wall* []
422 (let [node (Node. "brick-wall")]
423 (dorun
424 (map
425 (comp #(.attachChild node %) brick*)
426 (for [y (range 10)
427 x (range 4)
428 z (range 1)]
429 (Vector3f.
430 (+ (* 2 x brick-length)
431 (if (even? (+ y z))
432 (/ brick-length 4) (/ brick-length -4)))
433 (+ (* brick-height (inc (* 2 y))))
434 (* 2 z brick-width) ))))
435 (.setShadowMode node RenderQueue$ShadowMode/CastAndReceive)
436 node))
439 #+end_src
442 *** Debug Actions
443 #+name: debug-actions
444 #+begin_src clojure :results silent
445 (in-ns 'cortex.util)
447 (defn basic-light-setup
448 "returns a sequence of lights appropriate for fully lighting a scene"
449 []
450 (conj
451 (doall
452 (map
453 (fn [direction]
454 (doto (DirectionalLight.)
455 (.setDirection direction)
456 (.setColor ColorRGBA/White)))
457 [;; six faces of a cube
458 Vector3f/UNIT_X
459 Vector3f/UNIT_Y
460 Vector3f/UNIT_Z
461 (.mult Vector3f/UNIT_X (float -1))
462 (.mult Vector3f/UNIT_Y (float -1))
463 (.mult Vector3f/UNIT_Z (float -1))]))
464 (doto (AmbientLight.)
465 (.setColor ColorRGBA/White))))
467 (defn light-up-everything
468 "Add lights to a world appropriate for quickly seeing everything
469 in the scene. Adds six DirectionalLights facing in orthogonal
470 directions, and one AmbientLight to provide overall lighting
471 coverage."
472 [world]
473 (dorun
474 (map
475 #(.addLight (.getRootNode world) %)
476 (basic-light-setup))))
478 (defn fire-cannon-ball
479 "Creates a function that fires a cannon-ball from the current game's
480 camera. The cannon-ball will be attached to the node if provided, or
481 to the game's RootNode if no node is provided."
482 ([node]
483 (fn [game value]
484 (if (not value)
485 (let [camera (.getCamera game)
486 cannon-ball
487 (sphere 0.4
488 ;;:texture nil
489 :material "Common/MatDefs/Misc/Unshaded.j3md"
490 :color ColorRGBA/Blue
491 :name "cannonball!"
492 :position
493 (.add (.getLocation camera)
494 (.mult (.getDirection camera) (float 1)))
495 :mass 25)] ;200 0.05
496 (.setLinearVelocity
497 (.getControl cannon-ball RigidBodyControl)
498 (.mult (.getDirection camera) (float 50))) ;50
499 (add-element game cannon-ball (if node node (.getRootNode
500 game)))
501 cannon-ball))))
502 ([]
503 (fire-cannon-ball false)))
505 (def standard-debug-controls
506 {"key-space" (fire-cannon-ball)})
509 (defn tap [obj direction force]
510 (let [control (.getControl obj RigidBodyControl)]
511 (.applyTorque
512 control
513 (.mult (.getPhysicsRotation control)
514 (.mult (.normalize direction) (float force))))))
517 (defn with-movement
518 [object
519 [up down left right roll-up roll-down :as keyboard]
520 forces
521 [root-node
522 keymap
523 initialization
524 world-loop]]
525 (let [add-keypress
526 (fn [state keymap key]
527 (merge keymap
528 {key
529 (fn [_ pressed?]
530 (reset! state pressed?))}))
531 move-up? (atom false)
532 move-down? (atom false)
533 move-left? (atom false)
534 move-right? (atom false)
535 roll-left? (atom false)
536 roll-right? (atom false)
538 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
539 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
540 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
541 atoms [move-left? move-right? move-up? move-down?
542 roll-left? roll-right?]
544 keymap* (reduce merge
545 (map #(add-keypress %1 keymap %2)
546 atoms
547 keyboard))
549 splice-loop (fn []
550 (dorun
551 (map
552 (fn [sym direction force]
553 (if @sym
554 (tap object direction force)))
555 atoms directions forces)))
557 world-loop* (fn [world tpf]
558 (world-loop world tpf)
559 (splice-loop))]
560 [root-node
561 keymap*
562 initialization
563 world-loop*]))
565 (import com.jme3.font.BitmapText)
566 (import com.jme3.scene.control.AbstractControl)
567 (import com.aurellem.capture.IsoTimer)
569 (defn display-dilated-time
570 "Shows the time as it is flowing in the simulation on a HUD display.
571 Useful for making videos."
572 [world timer]
573 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
574 text (BitmapText. font false)]
575 (.setLocalTranslation text 300 (.getLineHeight text) 0)
576 (.addControl
577 text
578 (proxy [AbstractControl] []
579 (controlUpdate [tpf]
580 (.setText text (format
581 "%.2f"
582 (float (.getTimeInSeconds timer)))))
583 (controlRender [_ _])))
584 (.attachChild (.getGuiNode world) text)))
585 #+end_src
588 *** Viewing Objects
590 #+name: world-view
591 #+begin_src clojure :results silent
592 (in-ns 'cortex.util)
594 (defprotocol Viewable
595 (view [something]))
597 (extend-type com.jme3.scene.Geometry
598 Viewable
599 (view [geo]
600 (view (doto (Node.)(.attachChild geo)))))
602 (extend-type com.jme3.scene.Node
603 Viewable
604 (view
605 [node]
606 (.start
607 (world
608 node
609 {}
610 (fn [world]
611 (enable-debug world)
612 (set-gravity world Vector3f/ZERO)
613 (light-up-everything world))
614 no-op))))
616 (extend-type com.jme3.math.ColorRGBA
617 Viewable
618 (view
619 [color]
620 (view (doto (Node.)
621 (.attachChild (box 1 1 1 :color color))))))
623 (extend-type ij.ImagePlus
624 Viewable
625 (view [image]
626 (.show image)))
628 (extend-type java.awt.image.BufferedImage
629 Viewable
630 (view
631 [image]
632 (view (ImagePlus. "view-buffered-image" image))))
635 (defprotocol Textual
636 (text [something]
637 "Display a detailed textual analysis of the given object."))
639 (extend-type com.jme3.scene.Node
640 Textual
641 (text [node]
642 (println "Total Vertexes: " (.getVertexCount node))
643 (println "Total Triangles: " (.getTriangleCount node))
644 (println "Controls :")
645 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
646 (println "Has " (.getQuantity node) " Children:")
647 (doall (map text (.getChildren node)))))
649 (extend-type com.jme3.animation.AnimControl
650 Textual
651 (text [control]
652 (let [animations (.getAnimationNames control)]
653 (println "Animation Control with " (count animations) " animation(s):")
654 (dorun (map println animations)))))
656 (extend-type com.jme3.animation.SkeletonControl
657 Textual
658 (text [control]
659 (println "Skeleton Control with the following skeleton:")
660 (println (.getSkeleton control))))
662 (extend-type com.jme3.bullet.control.KinematicRagdollControl
663 Textual
664 (text [control]
665 (println "Ragdoll Control")))
667 (extend-type com.jme3.scene.Geometry
668 Textual
669 (text [control]
670 (println "...geo...")))
672 (extend-type Triangle
673 Textual
674 (text [t]
675 (println "Triangle: " \newline (.get1 t) \newline
676 (.get2 t) \newline (.get3 t))))
678 #+end_src
680 Here I make the =Viewable= protocol and extend it to JME's types. Now
681 JME3's =hello-world= can be written as easily as:
683 #+begin_src clojure :results silent
684 (cortex.util/view (cortex.util/box))
685 #+end_src
688 * code generation
689 #+begin_src clojure :tangle ../src/cortex/import.clj
690 <<import>>
691 #+end_src
694 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
695 <<util>>
696 <<shapes>>
697 <<debug-actions>>
698 <<world-view>>
699 #+end_src