view org/util.org @ 114:9d0fe7f54e14

merged image viewing code to cortex.util
author Robert McIntyre <rlm@mit.edu>
date Thu, 19 Jan 2012 22:19:24 -0700
parents 92b857b6145d
children b591da250afc
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 usefull for debugging purposes since
56 it allows completion for almost all of JME's classes from the REPL.
58 Out of curiousity, 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 (:use clojure.contrib.def)
87 (:import com.jme3.math.Vector3f)
88 (:import com.jme3.math.Quaternion)
89 (:import com.jme3.asset.TextureKey)
90 (:import com.jme3.bullet.control.RigidBodyControl)
91 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
92 (:import com.jme3.scene.shape.Box)
93 (:import com.jme3.scene.Node)
94 (:import com.jme3.scene.shape.Sphere)
95 (:import com.jme3.light.AmbientLight)
96 (:import com.jme3.light.DirectionalLight)
97 (:import (com.jme3.math Triangle ColorRGBA))
98 (:import com.jme3.bullet.BulletAppState)
99 (:import com.jme3.material.Material)
100 (:import com.jme3.scene.Geometry)
101 (:import java.awt.image.BufferedImage)
102 (:import javax.swing.JPanel)
103 (:import javax.swing.JFrame)
104 (:import javax.swing.SwingUtilities)
106 (:import (java.util.logging Level Logger)))
108 (defvar println-repl
109 (bound-fn [& args] (apply println args))
110 "println called from the LWJGL thread will not go to the REPL, but
111 instead to whatever terminal started the JVM process. This function
112 will always output to the REPL")
114 (defn position-camera
115 "Change the position of the in-world camera."
116 ([world position direction up]
117 (doto (.getCamera world)
118 (.setLocation )
119 (.lookAt direction up)))
120 ([world position direction]
121 (position-camera
122 world position direction Vector3f/UNIT_Y)))
124 (defn enable-debug
125 "Turn on debug wireframes for every object in this simulation."
126 [world]
127 (.enableDebug
128 (.getPhysicsSpace
129 (.getState
130 (.getStateManager world)
131 BulletAppState))
132 (asset-manager)))
134 (defn speed-up
135 "Increase the dismally slow speed of the world's camera."
136 [world]
137 (.setMoveSpeed (.getFlyByCamera world)
138 (float 100))
139 (.setRotationSpeed (.getFlyByCamera world)
140 (float 20))
141 world)
144 (defn no-logging
145 "Disable all of jMonkeyEngine's logging."
146 []
147 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
149 (defn set-accuracy
150 "Change the accuracy at which the World's Physics is calculated."
151 [world new-accuracy]
152 (let [physics-manager
153 (.getState
154 (.getStateManager world) BulletAppState)]
155 (.setAccuracy
156 (.getPhysicsSpace physics-manager)
157 (float new-accuracy))))
160 (defn set-gravity
161 "In order to change the gravity of a scene, it is not only necessary
162 to set the gravity variable, but to \"tap\" every physics object in
163 the scene to reactivate physics calculations."
164 [world gravity]
165 (traverse
166 (fn [geom]
167 (if-let
168 ;; only set gravity for physical objects.
169 [control (.getControl geom RigidBodyControl)]
170 (do
171 (.setGravity control gravity)
172 ;; tappsies!
173 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
174 (.getRootNode world)))
176 (defn add-element
177 "Add the Spatial to the world's environment"
178 ([world element node]
179 (.addAll
180 (.getPhysicsSpace
181 (.getState
182 (.getStateManager world)
183 BulletAppState))
184 element)
185 (.attachChild node element))
186 ([world element]
187 (add-element world element (.getRootNode world))))
189 (defn apply-map
190 "Like apply, but works for maps and functions that expect an
191 implicit map and nothing else as in (fn [& {}]).
192 ------- Example -------
193 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
194 (println www))
195 (apply-map demo {:www \"hello!\"})
196 -->\"hello\""
197 [fn m]
198 (apply fn (reduce #(into %1 %2) [] m)))
200 #+end_src
202 #+results: util
203 : #'cortex.util/apply-map
206 *** Creating Basic Shapes
208 #+name: shapes
209 #+begin_src clojure :results silent
210 (in-ns 'cortex.util)
212 (defrecord shape-description
213 [name
214 color
215 mass
216 friction
217 texture
218 material
219 position
220 rotation
221 shape
222 physical?
223 GImpact?
224 ])
226 (defvar base-shape
227 (shape-description.
228 "default-shape"
229 false
230 ;;ColorRGBA/Blue
231 1.0 ;; mass
232 1.0 ;; friction
233 ;; texture
234 "Textures/Terrain/BrickWall/BrickWall.jpg"
235 ;; material
236 "Common/MatDefs/Misc/Unshaded.j3md"
237 Vector3f/ZERO
238 Quaternion/IDENTITY
239 (Box. Vector3f/ZERO 0.5 0.5 0.5)
240 true
241 false)
242 "Basic settings for shapes.")
244 (defn make-shape
245 [#^shape-description d]
246 (let [asset-manager (asset-manager)
247 mat (Material. asset-manager (:material d))
248 geom (Geometry. (:name d) (:shape d))]
249 (if (:texture d)
250 (let [key (TextureKey. (:texture d))]
251 ;;(.setGenerateMips key true)
252 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
253 ))
254 (if (:color d) (.setColor mat "Color" (:color d)))
255 (.setMaterial geom mat)
256 (if-let [rotation (:rotation d)] (.rotate geom rotation))
257 (.setLocalTranslation geom (:position d))
258 (if (:physical? d)
259 (let [physics-control
260 (if (:GImpact d)
261 ;; Create an accurate mesh collision shape if desired.
262 (RigidBodyControl.
263 (doto (GImpactCollisionShape.
264 (.getMesh geom))
265 (.createJmeMesh)
266 ;;(.setMargin 0)
267 )
268 (float (:mass d)))
269 ;; otherwise use jme3's default
270 (RigidBodyControl. (float (:mass d))))]
271 (.addControl geom physics-control)
272 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
273 (.setFriction physics-control (:friction d))))
274 geom))
276 (defn box
277 ([l w h & {:as options}]
278 (let [options (merge base-shape options)]
279 (make-shape (assoc options
280 :shape (Box. l w h)))))
281 ([] (box 0.5 0.5 0.5)))
283 (defn sphere
284 ([r & {:as options}]
285 (let [options (merge base-shape options)]
286 (make-shape (assoc options
287 :shape (Sphere. 32 32 (float r))))))
288 ([] (sphere 0.5)))
290 (defn green-x-ray
291 "A usefull material for debuging -- it can be seen no matter what
292 object occuldes it."
293 []
294 (doto (Material. (asset-manager)
295 "Common/MatDefs/Misc/Unshaded.j3md")
296 (.setColor "Color" ColorRGBA/Green)
297 (-> (.getAdditionalRenderState)
298 (.setDepthTest false))))
300 (defn node-seq
301 "Take a node and return a seq of all its children
302 recursively. There will be no nodes left in the resulting
303 structure"
304 [#^Node node]
305 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
307 (defn nodify
308 "Take a sequence of things that can be attached to a node and return
309 a node with all of them attached"
310 ([name children]
311 (let [node (Node. name)]
312 (dorun (map #(.attachChild node %) children))
313 node))
314 ([children] (nodify "" children)))
317 #+end_src
320 *** Debug Actions
321 #+name: debug-actions
322 #+begin_src clojure :results silent
323 (in-ns 'cortex.util)
325 (defn basic-light-setup
326 "returns a sequence of lights appropiate for fully lighting a scene"
327 []
328 (conj
329 (doall
330 (map
331 (fn [direction]
332 (doto (DirectionalLight.)
333 (.setDirection direction)
334 (.setColor ColorRGBA/White)))
335 [;; six faces of a cube
336 Vector3f/UNIT_X
337 Vector3f/UNIT_Y
338 Vector3f/UNIT_Z
339 (.mult Vector3f/UNIT_X (float -1))
340 (.mult Vector3f/UNIT_Y (float -1))
341 (.mult Vector3f/UNIT_Z (float -1))]))
342 (doto (AmbientLight.)
343 (.setColor ColorRGBA/White))))
345 (defn light-up-everything
346 "Add lights to a world appropiate for quickly seeing everything
347 in the scene. Adds six DirectionalLights facing in orthogonal
348 directions, and one AmbientLight to provide overall lighting
349 coverage."
350 [world]
351 (dorun
352 (map
353 #(.addLight (.getRootNode world) %)
354 (basic-light-setup))))
356 (defn fire-cannon-ball
357 "Creates a function that fires a cannon-ball from the current game's
358 camera. The cannon-ball will be attached to the node if provided, or
359 to the game's RootNode if no node is provided."
360 ([node]
361 (fn [game value]
362 (if (not value)
363 (let [camera (.getCamera game)
364 cannon-ball
365 (sphere 0.7
366 :material "Common/MatDefs/Misc/Unshaded.j3md"
367 :texture "Textures/PokeCopper.jpg"
368 :position
369 (.add (.getLocation camera)
370 (.mult (.getDirection camera) (float 1)))
371 :mass 3)] ;200 0.05
372 (.setLinearVelocity
373 (.getControl cannon-ball RigidBodyControl)
374 (.mult (.getDirection camera) (float 50))) ;50
375 (add-element game cannon-ball (if node node (.getRootNode game)))))))
376 ([]
377 (fire-cannon-ball false)))
379 (def standard-debug-controls
380 {"key-space" (fire-cannon-ball)})
385 #+end_src
388 *** Viewing Objects
390 #+name: world-view
391 #+begin_src clojure :results silent
392 (in-ns 'cortex.util)
394 (defn view-image
395 "Initailizes a JPanel on which you may draw a BufferedImage.
396 Returns a function that accepts a BufferedImage and draws it to the
397 JPanel."
398 []
399 (let [image
400 (atom
401 (BufferedImage. 1 1 BufferedImage/TYPE_4BYTE_ABGR))
402 panel
403 (proxy [JPanel] []
404 (paint
405 [graphics]
406 (proxy-super paintComponent graphics)
407 (.drawImage graphics @image 0 0 nil)))
408 frame (JFrame. "Display Image")]
409 (SwingUtilities/invokeLater
410 (fn []
411 (doto frame
412 (-> (.getContentPane) (.add panel))
413 (.pack)
414 (.setLocationRelativeTo nil)
415 (.setResizable true)
416 (.setVisible true))))
417 (fn [#^BufferedImage i]
418 (reset! image i)
419 (.setSize frame (+ 8 (.getWidth i)) (+ 28 (.getHeight i)))
420 (.repaint panel 0 0 (.getWidth i) (.getHeight i)))))
422 (defprotocol Viewable
423 (view [something]))
425 (extend-type com.jme3.scene.Geometry
426 Viewable
427 (view [geo]
428 (view (doto (Node.)(.attachChild geo)))))
430 (extend-type com.jme3.scene.Node
431 Viewable
432 (view
433 [node]
434 (.start
435 (world
436 node
437 {}
438 (fn [world]
439 (enable-debug world)
440 (set-gravity world Vector3f/ZERO)
441 (light-up-everything world))
442 no-op))))
444 (extend-type com.jme3.math.ColorRGBA
445 Viewable
446 (view
447 [color]
448 (view (doto (Node.)
449 (.attachChild (box 1 1 1 :color color))))))
451 (defprotocol Textual
452 (text [something]
453 "Display a detailed textual analysis of the given object."))
455 (extend-type com.jme3.scene.Node
456 Textual
457 (text [node]
458 (println "Total Vertexes: " (.getVertexCount node))
459 (println "Total Triangles: " (.getTriangleCount node))
460 (println "Controls :")
461 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
462 (println "Has " (.getQuantity node) " Children:")
463 (doall (map text (.getChildren node)))))
465 (extend-type com.jme3.animation.AnimControl
466 Textual
467 (text [control]
468 (let [animations (.getAnimationNames control)]
469 (println "Animation Control with " (count animations) " animation(s):")
470 (dorun (map println animations)))))
472 (extend-type com.jme3.animation.SkeletonControl
473 Textual
474 (text [control]
475 (println "Skeleton Control with the following skeleton:")
476 (println (.getSkeleton control))))
478 (extend-type com.jme3.bullet.control.KinematicRagdollControl
479 Textual
480 (text [control]
481 (println "Ragdoll Control")))
483 (extend-type com.jme3.scene.Geometry
484 Textual
485 (text [control]
486 (println "...geo...")))
488 (extend-type Triangle
489 Textual
490 (text [t]
491 (println "Triangle: " \newline (.get1 t) \newline
492 (.get2 t) \newline (.get3 t))))
494 #+end_src
496 Here I make the =Viewable= protocol and extend it to JME's types. Now
497 JME3's =hello-world= can be written as easily as:
499 #+begin_src clojure :results silent
500 (cortex.util/view (cortex.util/box))
501 #+end_src
504 * COMMENT code generation
505 #+begin_src clojure :tangle ../src/cortex/import.clj
506 <<import>>
507 #+end_src
510 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
511 <<util>>
512 <<shapes>>
513 <<debug-actions>>
514 <<world-view>>
515 #+end_src