view org/util.org @ 396:0898d93a39ef

make touch sensors much less concentrated.
author Robert McIntyre <rlm@mit.edu>
date Mon, 03 Mar 2014 23:04:02 -0500
parents 4c37d39a3cf6
children 6ba908c1a0a9 a44d8a28cbea
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-classes)) "classes")
78 #+end_src
80 #+results:
81 : 955 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))
368 #+end_src
371 *** Debug Actions
372 #+name: debug-actions
373 #+begin_src clojure :results silent
374 (in-ns 'cortex.util)
376 (defn basic-light-setup
377 "returns a sequence of lights appropriate for fully lighting a scene"
378 []
379 (conj
380 (doall
381 (map
382 (fn [direction]
383 (doto (DirectionalLight.)
384 (.setDirection direction)
385 (.setColor ColorRGBA/White)))
386 [;; six faces of a cube
387 Vector3f/UNIT_X
388 Vector3f/UNIT_Y
389 Vector3f/UNIT_Z
390 (.mult Vector3f/UNIT_X (float -1))
391 (.mult Vector3f/UNIT_Y (float -1))
392 (.mult Vector3f/UNIT_Z (float -1))]))
393 (doto (AmbientLight.)
394 (.setColor ColorRGBA/White))))
396 (defn light-up-everything
397 "Add lights to a world appropriate for quickly seeing everything
398 in the scene. Adds six DirectionalLights facing in orthogonal
399 directions, and one AmbientLight to provide overall lighting
400 coverage."
401 [world]
402 (dorun
403 (map
404 #(.addLight (.getRootNode world) %)
405 (basic-light-setup))))
407 (defn fire-cannon-ball
408 "Creates a function that fires a cannon-ball from the current game's
409 camera. The cannon-ball will be attached to the node if provided, or
410 to the game's RootNode if no node is provided."
411 ([node]
412 (fn [game value]
413 (if (not value)
414 (let [camera (.getCamera game)
415 cannon-ball
416 (sphere 0.4
417 ;;:texture nil
418 :material "Common/MatDefs/Misc/Unshaded.j3md"
419 :color ColorRGBA/Blue
420 :name "cannonball!"
421 :position
422 (.add (.getLocation camera)
423 (.mult (.getDirection camera) (float 1)))
424 :mass 25)] ;200 0.05
425 (.setLinearVelocity
426 (.getControl cannon-ball RigidBodyControl)
427 (.mult (.getDirection camera) (float 50))) ;50
428 (add-element game cannon-ball (if node node (.getRootNode
429 game)))
430 cannon-ball))))
431 ([]
432 (fire-cannon-ball false)))
434 (def standard-debug-controls
435 {"key-space" (fire-cannon-ball)})
438 (defn tap [obj direction force]
439 (let [control (.getControl obj RigidBodyControl)]
440 (.applyTorque
441 control
442 (.mult (.getPhysicsRotation control)
443 (.mult (.normalize direction) (float force))))))
446 (defn with-movement
447 [object
448 [up down left right roll-up roll-down :as keyboard]
449 forces
450 [root-node
451 keymap
452 initialization
453 world-loop]]
454 (let [add-keypress
455 (fn [state keymap key]
456 (merge keymap
457 {key
458 (fn [_ pressed?]
459 (reset! state pressed?))}))
460 move-up? (atom false)
461 move-down? (atom false)
462 move-left? (atom false)
463 move-right? (atom false)
464 roll-left? (atom false)
465 roll-right? (atom false)
467 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)
468 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)
469 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]
470 atoms [move-left? move-right? move-up? move-down?
471 roll-left? roll-right?]
473 keymap* (reduce merge
474 (map #(add-keypress %1 keymap %2)
475 atoms
476 keyboard))
478 splice-loop (fn []
479 (dorun
480 (map
481 (fn [sym direction force]
482 (if @sym
483 (tap object direction force)))
484 atoms directions forces)))
486 world-loop* (fn [world tpf]
487 (world-loop world tpf)
488 (splice-loop))]
489 [root-node
490 keymap*
491 initialization
492 world-loop*]))
494 (import com.jme3.font.BitmapText)
495 (import com.jme3.scene.control.AbstractControl)
496 (import com.aurellem.capture.IsoTimer)
498 (defn display-dilated-time
499 "Shows the time as it is flowing in the simulation on a HUD display.
500 Useful for making videos."
501 [world timer]
502 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")
503 text (BitmapText. font false)]
504 (.setLocalTranslation text 300 (.getLineHeight text) 0)
505 (.addControl
506 text
507 (proxy [AbstractControl] []
508 (controlUpdate [tpf]
509 (.setText text (format
510 "%.2f"
511 (float (.getTimeInSeconds timer)))))
512 (controlRender [_ _])))
513 (.attachChild (.getGuiNode world) text)))
514 #+end_src
517 *** Viewing Objects
519 #+name: world-view
520 #+begin_src clojure :results silent
521 (in-ns 'cortex.util)
523 (defprotocol Viewable
524 (view [something]))
526 (extend-type com.jme3.scene.Geometry
527 Viewable
528 (view [geo]
529 (view (doto (Node.)(.attachChild geo)))))
531 (extend-type com.jme3.scene.Node
532 Viewable
533 (view
534 [node]
535 (.start
536 (world
537 node
538 {}
539 (fn [world]
540 (enable-debug world)
541 (set-gravity world Vector3f/ZERO)
542 (light-up-everything world))
543 no-op))))
545 (extend-type com.jme3.math.ColorRGBA
546 Viewable
547 (view
548 [color]
549 (view (doto (Node.)
550 (.attachChild (box 1 1 1 :color color))))))
552 (extend-type ij.ImagePlus
553 Viewable
554 (view [image]
555 (.show image)))
557 (extend-type java.awt.image.BufferedImage
558 Viewable
559 (view
560 [image]
561 (view (ImagePlus. "view-buffered-image" image))))
564 (defprotocol Textual
565 (text [something]
566 "Display a detailed textual analysis of the given object."))
568 (extend-type com.jme3.scene.Node
569 Textual
570 (text [node]
571 (println "Total Vertexes: " (.getVertexCount node))
572 (println "Total Triangles: " (.getTriangleCount node))
573 (println "Controls :")
574 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
575 (println "Has " (.getQuantity node) " Children:")
576 (doall (map text (.getChildren node)))))
578 (extend-type com.jme3.animation.AnimControl
579 Textual
580 (text [control]
581 (let [animations (.getAnimationNames control)]
582 (println "Animation Control with " (count animations) " animation(s):")
583 (dorun (map println animations)))))
585 (extend-type com.jme3.animation.SkeletonControl
586 Textual
587 (text [control]
588 (println "Skeleton Control with the following skeleton:")
589 (println (.getSkeleton control))))
591 (extend-type com.jme3.bullet.control.KinematicRagdollControl
592 Textual
593 (text [control]
594 (println "Ragdoll Control")))
596 (extend-type com.jme3.scene.Geometry
597 Textual
598 (text [control]
599 (println "...geo...")))
601 (extend-type Triangle
602 Textual
603 (text [t]
604 (println "Triangle: " \newline (.get1 t) \newline
605 (.get2 t) \newline (.get3 t))))
607 #+end_src
609 Here I make the =Viewable= protocol and extend it to JME's types. Now
610 JME3's =hello-world= can be written as easily as:
612 #+begin_src clojure :results silent
613 (cortex.util/view (cortex.util/box))
614 #+end_src
617 * COMMENT code generation
618 #+begin_src clojure :tangle ../src/cortex/import.clj
619 <<import>>
620 #+end_src
623 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
624 <<util>>
625 <<shapes>>
626 <<debug-actions>>
627 <<world-view>>
628 #+end_src