view org/util.org @ 459:a86555b02916

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