view org/util.org @ 335:5dcd44576cbc

add BufferedImage to Viewable Protocol
author Robert McIntyre <rlm@mit.edu>
date Fri, 20 Jul 2012 13:02:58 -0500
parents 52de8a36edde
children 70469ff8eb56
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.7
402 :material "Common/MatDefs/Misc/Unshaded.j3md"
403 :color ColorRGBA/White
404 :name "cannonball!"
405 :position
406 (.add (.getLocation camera)
407 (.mult (.getDirection camera) (float 1)))
408 :mass 3)] ;200 0.05
409 (.setLinearVelocity
410 (.getControl cannon-ball RigidBodyControl)
411 (.mult (.getDirection camera) (float 50))) ;50
412 (add-element game cannon-ball (if node node (.getRootNode
413 game)))
414 cannon-ball))))
415 ([]
416 (fire-cannon-ball false)))
418 (def standard-debug-controls
419 {"key-space" (fire-cannon-ball)})
422 (defn tap [obj direction force]
423 (let [control (.getControl obj RigidBodyControl)]
424 (.applyTorque
425 control
426 (.mult (.getPhysicsRotation control)
427 (.mult (.normalize direction) (float force))))))
430 (defn with-movement
431 [object
432 [up down left right roll-up roll-down :as keyboard]
433 forces
434 [root-node
435 keymap
436 initialization
437 world-loop]]
438 (let [add-keypress
439 (fn [state keymap key]
440 (merge keymap
441 {key
442 (fn [_ pressed?]
443 (reset! state pressed?))}))
444 move-up? (atom false)
445 move-down? (atom false)
446 move-left? (atom false)
447 move-right? (atom false)
448 roll-left? (atom false)
449 roll-right? (atom false)
451 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
452 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
453 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
454 atoms [move-left? move-right? move-up? move-down?
455 roll-left? roll-right?]
457 keymap* (reduce merge
458 (map #(add-keypress %1 keymap %2)
459 atoms
460 keyboard))
462 splice-loop (fn []
463 (dorun
464 (map
465 (fn [sym direction force]
466 (if @sym
467 (tap object direction force)))
468 atoms directions forces)))
470 world-loop* (fn [world tpf]
471 (world-loop world tpf)
472 (splice-loop))]
473 [root-node
474 keymap*
475 initialization
476 world-loop*]))
478 (import com.jme3.font.BitmapText)
479 (import com.jme3.scene.control.AbstractControl)
480 (import com.aurellem.capture.IsoTimer)
482 (defn display-dilated-time
483 "Shows the time as it is flowing in the simulation on a HUD display.
484 Useful for making videos."
485 [world timer]
486 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
487 text (BitmapText. font false)]
488 (.setLocalTranslation text 300 (.getLineHeight text) 0)
489 (.addControl
490 text
491 (proxy [AbstractControl] []
492 (controlUpdate [tpf]
493 (.setText text (format
494 "%.2f"
495 (float (/ (.getTime timer) 1000)))))
496 (controlRender [_ _])))
497 (.attachChild (.getGuiNode world) text)))
498 #+end_src
501 *** Viewing Objects
503 #+name: world-view
504 #+begin_src clojure :results silent
505 (in-ns 'cortex.util)
507 (defprotocol Viewable
508 (view [something]))
510 (extend-type com.jme3.scene.Geometry
511 Viewable
512 (view [geo]
513 (view (doto (Node.)(.attachChild geo)))))
515 (extend-type com.jme3.scene.Node
516 Viewable
517 (view
518 [node]
519 (.start
520 (world
521 node
522 {}
523 (fn [world]
524 (enable-debug world)
525 (set-gravity world Vector3f/ZERO)
526 (light-up-everything world))
527 no-op))))
529 (extend-type com.jme3.math.ColorRGBA
530 Viewable
531 (view
532 [color]
533 (view (doto (Node.)
534 (.attachChild (box 1 1 1 :color color))))))
536 (extend-type java.awt.image.BufferedImage
537 Viewable
538 (view
539 [image]
540 (.show (ImagePlus. "view-buffered-image" image))))
542 (defprotocol Textual
543 (text [something]
544 "Display a detailed textual analysis of the given object."))
546 (extend-type com.jme3.scene.Node
547 Textual
548 (text [node]
549 (println "Total Vertexes: " (.getVertexCount node))
550 (println "Total Triangles: " (.getTriangleCount node))
551 (println "Controls :")
552 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
553 (println "Has " (.getQuantity node) " Children:")
554 (doall (map text (.getChildren node)))))
556 (extend-type com.jme3.animation.AnimControl
557 Textual
558 (text [control]
559 (let [animations (.getAnimationNames control)]
560 (println "Animation Control with " (count animations) " animation(s):")
561 (dorun (map println animations)))))
563 (extend-type com.jme3.animation.SkeletonControl
564 Textual
565 (text [control]
566 (println "Skeleton Control with the following skeleton:")
567 (println (.getSkeleton control))))
569 (extend-type com.jme3.bullet.control.KinematicRagdollControl
570 Textual
571 (text [control]
572 (println "Ragdoll Control")))
574 (extend-type com.jme3.scene.Geometry
575 Textual
576 (text [control]
577 (println "...geo...")))
579 (extend-type Triangle
580 Textual
581 (text [t]
582 (println "Triangle: " \newline (.get1 t) \newline
583 (.get2 t) \newline (.get3 t))))
585 #+end_src
587 Here I make the =Viewable= protocol and extend it to JME's types. Now
588 JME3's =hello-world= can be written as easily as:
590 #+begin_src clojure :results silent
591 (cortex.util/view (cortex.util/box))
592 #+end_src
595 * COMMENT code generation
596 #+begin_src clojure :tangle ../src/cortex/import.clj
597 <<import>>
598 #+end_src
601 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
602 <<util>>
603 <<shapes>>
604 <<debug-actions>>
605 <<world-view>>
606 #+end_src