view org/util.org @ 76:5e75b616ca60

going to work on constraints for the joints in the hand.
author Robert McIntyre <rlm@mit.edu>
date Wed, 28 Dec 2011 23:12:32 -0700
parents fb810a2c50c2
children 77b506ac64f3
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.ColorRGBA)
98 (:import com.jme3.bullet.BulletAppState)
99 (:import com.jme3.material.Material)
100 (:import com.jme3.scene.Geometry)
101 (:import (java.util.logging Level Logger)))
105 (defvar println-repl
106 (bound-fn [& args] (apply println args))
107 "println called from the LWJGL thread will not go to the REPL, but
108 instead to whatever terminal started the JVM process. This function
109 will always output to the REPL")
111 (defn position-camera
112 "Change the position of the in-world camera."
113 ([world position direction up]
114 (doto (.getCamera world)
115 (.setLocation )
116 (.lookAt direction up)))
117 ([world position direction]
118 (position-camera
119 world position direction Vector3f/UNIT_Y)))
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 no-logging
132 "Disable all of jMonkeyEngine's logging."
133 []
134 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
136 (defn set-accuracy
137 "Change the accuracy at which the World's Physics is calculated."
138 [world new-accuracy]
139 (let [physics-manager
140 (.getState
141 (.getStateManager world) BulletAppState)]
142 (.setAccuracy
143 (.getPhysicsSpace physics-manager)
144 (float new-accuracy))))
147 (defn set-gravity
148 "In order to change the gravity of a scene, it is not only necessary
149 to set the gravity variable, but to \"tap\" every physics object in
150 the scene to reactivate physics calculations."
151 [world gravity]
152 (traverse
153 (fn [geom]
154 (if-let
155 ;; only set gravity for physical objects.
156 [control (.getControl geom RigidBodyControl)]
157 (do
158 (.setGravity control gravity)
159 ;; tappsies!
160 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
161 (.getRootNode world)))
163 (defn add-element
164 "Add the Spatial to the world's environment"
165 ([world element node]
166 (.addAll
167 (.getPhysicsSpace
168 (.getState
169 (.getStateManager world)
170 BulletAppState))
171 element)
172 (.attachChild node element))
173 ([world element]
174 (add-element world element (.getRootNode world))))
176 (defn apply-map
177 "Like apply, but works for maps and functions that expect an
178 implicit map and nothing else as in (fn [& {}]).
179 ------- Example -------
180 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
181 (println www))
182 (apply-map demo {:www \"hello!\"})
183 -->\"hello\""
184 [fn m]
185 (apply fn (reduce #(into %1 %2) [] m)))
187 #+end_src
189 #+results: util
190 : #'cortex.util/apply-map
193 *** Creating Basic Shapes
195 #+name: shapes
196 #+begin_src clojure :results silent
197 (in-ns 'cortex.util)
199 (defrecord shape-description
200 [name
201 color
202 mass
203 friction
204 texture
205 material
206 position
207 rotation
208 shape
209 physical?
210 GImpact?
211 ])
213 (defvar base-shape
214 (shape-description.
215 "default-shape"
216 false
217 ;;ColorRGBA/Blue
218 1.0 ;; mass
219 1.0 ;; friction
220 ;; texture
221 "Textures/Terrain/BrickWall/BrickWall.jpg"
222 ;; material
223 "Common/MatDefs/Misc/Unshaded.j3md"
224 Vector3f/ZERO
225 Quaternion/IDENTITY
226 (Box. Vector3f/ZERO 0.5 0.5 0.5)
227 true
228 false)
229 "Basic settings for shapes.")
231 (defn make-shape
232 [#^shape-description d]
233 (let [asset-manager (asset-manager)
234 mat (Material. asset-manager (:material d))
235 geom (Geometry. (:name d) (:shape d))]
236 (if (:texture d)
237 (let [key (TextureKey. (:texture d))]
238 ;;(.setGenerateMips key true)
239 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
240 ))
241 (if (:color d) (.setColor mat "Color" (:color d)))
242 (.setMaterial geom mat)
243 (if-let [rotation (:rotation d)] (.rotate geom rotation))
244 (.setLocalTranslation geom (:position d))
245 (if (:physical? d)
246 (let [physics-control
247 (if (:GImpact d)
248 ;; Create an accurate mesh collision shape if desired.
249 (RigidBodyControl.
250 (doto (GImpactCollisionShape.
251 (.getMesh geom))
252 (.createJmeMesh)
253 ;;(.setMargin 0)
254 )
255 (float (:mass d)))
256 ;; otherwise use jme3's default
257 (RigidBodyControl. (float (:mass d))))]
258 (.addControl geom physics-control)
259 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
260 (.setFriction physics-control (:friction d))))
261 geom))
263 (defn box
264 ([l w h & {:as options}]
265 (let [options (merge base-shape options)]
266 (make-shape (assoc options
267 :shape (Box. l w h)))))
268 ([] (box 0.5 0.5 0.5)))
270 (defn sphere
271 ([r & {:as options}]
272 (let [options (merge base-shape options)]
273 (make-shape (assoc options
274 :shape (Sphere. 32 32 (float r))))))
275 ([] (sphere 0.5)))
277 (defn green-x-ray
278 "A usefull material for debuging -- it can be seen no matter what
279 object occuldes it."
280 []
281 (doto (Material. (asset-manager)
282 "Common/MatDefs/Misc/Unshaded.j3md")
283 (.setColor "Color" ColorRGBA/Green)
284 (-> (.getAdditionalRenderState)
285 (.setDepthTest false))))
287 (defn node-seq
288 "Take a node and return a seq of all its children
289 recursively. There will be no nodes left in the resulting
290 structure"
291 [#^Node node]
292 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
294 (defn nodify
295 "Take a sequence of things that can be attached to a node and return
296 a node with all of them attached"
297 ([name children]
298 (let [node (Node. name)]
299 (dorun (map #(.attachChild node %) children))
300 node))
301 ([children] (nodify "" children)))
304 #+end_src
307 *** Debug Actions
308 #+name: debug-actions
309 #+begin_src clojure :results silent
310 (in-ns 'cortex.util)
312 (defn basic-light-setup
313 "returns a sequence of lights appropiate for fully lighting a scene"
314 []
315 (conj
316 (doall
317 (map
318 (fn [direction]
319 (doto (DirectionalLight.)
320 (.setDirection direction)
321 (.setColor ColorRGBA/White)))
322 [;; six faces of a cube
323 Vector3f/UNIT_X
324 Vector3f/UNIT_Y
325 Vector3f/UNIT_Z
326 (.mult Vector3f/UNIT_X (float -1))
327 (.mult Vector3f/UNIT_Y (float -1))
328 (.mult Vector3f/UNIT_Z (float -1))]))
329 (doto (AmbientLight.)
330 (.setColor ColorRGBA/White))))
332 (defn light-up-everything
333 "Add lights to a world appropiate for quickly seeing everything
334 in the scene. Adds six DirectionalLights facing in orthogonal
335 directions, and one AmbientLight to provide overall lighting
336 coverage."
337 [world]
338 (dorun
339 (map
340 #(.addLight (.getRootNode world) %)
341 (basic-light-setup))))
343 (defn fire-cannon-ball
344 "Creates a function that fires a cannon-ball from the current game's
345 camera. The cannon-ball will be attached to the node if provided, or
346 to the game's RootNode if no node is provided."
347 ([node]
348 (fn [game value]
349 (if (not value)
350 (let [camera (.getCamera game)
351 cannon-ball
352 (sphere 0.7
353 :material "Common/MatDefs/Misc/Unshaded.j3md"
354 :texture "Textures/PokeCopper.jpg"
355 :position
356 (.add (.getLocation camera)
357 (.mult (.getDirection camera) (float 1)))
358 :mass 3)] ;200 0.05
359 (.setLinearVelocity
360 (.getControl cannon-ball RigidBodyControl)
361 (.mult (.getDirection camera) (float 50))) ;50
362 (add-element game cannon-ball (if node node (.getRootNode game)))))))
363 ([]
364 (fire-cannon-ball false)))
366 (def standard-debug-controls
367 {"key-space" (fire-cannon-ball)})
372 #+end_src
375 *** Viewing Objects
377 #+name: world-view
378 #+begin_src clojure :results silent
379 (in-ns 'cortex.util)
381 (defprotocol Viewable
382 (view [something]))
384 (extend-type com.jme3.scene.Geometry
385 Viewable
386 (view [geo]
387 (view (doto (Node.)(.attachChild geo)))))
389 (extend-type com.jme3.scene.Node
390 Viewable
391 (view
392 [node]
393 (.start
394 (world
395 node
396 {}
397 (fn [world]
398 (enable-debug world)
399 (set-gravity world Vector3f/ZERO)
400 (light-up-everything world))
401 no-op))))
403 (extend-type com.jme3.math.ColorRGBA
404 Viewable
405 (view
406 [color]
407 (view (doto (Node.)
408 (.attachChild (box 1 1 1 :color color))))))
410 (defprotocol Textual
411 (text [something]
412 "Display a detailed textual analysis of the given object."))
414 (extend-type com.jme3.scene.Node
415 Textual
416 (text [node]
417 (println "Total Vertexes: " (.getVertexCount node))
418 (println "Total Triangles: " (.getTriangleCount node))
419 (println "Controls :")
420 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
421 (println "Has " (.getQuantity node) " Children:")
422 (doall (map text (.getChildren node)))))
424 (extend-type com.jme3.animation.AnimControl
425 Textual
426 (text [control]
427 (let [animations (.getAnimationNames control)]
428 (println "Animation Control with " (count animations) " animation(s):")
429 (dorun (map println animations)))))
431 (extend-type com.jme3.animation.SkeletonControl
432 Textual
433 (text [control]
434 (println "Skeleton Control with the following skeleton:")
435 (println (.getSkeleton control))))
437 (extend-type com.jme3.bullet.control.KinematicRagdollControl
438 Textual
439 (text [control]
440 (println "Ragdoll Control")))
442 (extend-type com.jme3.scene.Geometry
443 Textual
444 (text [control]
445 (println "...geo...")))
446 #+end_src
448 Here I make the =Viewable= protocol and extend it to JME's types. Now
449 JME3's =hello-world= can be written as easily as:
451 #+begin_src clojure :results silent
452 (cortex.util/view (cortex.util/box))
453 #+end_src
456 * COMMENT code generation
457 #+begin_src clojure :tangle ../src/cortex/import.clj
458 <<import>>
459 #+end_src
462 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
463 <<util>>
464 <<shapes>>
465 <<debug-actions>>
466 <<world-view>>
467 #+end_src